Vue3 核心模块源码解析(上)

news2024/11/25 18:58:17

Vue3相比大家也都有所了解,即使暂时没有使用上,但肯定也学习过!Vue3是使用TS进行重写,采用了MonoRepo的管理方式进行管理,本篇文章我们一起来看看 Vue3的使用,与Vue2有什么区别,以及我们该如何优雅的去使用?【中】篇会从源码的角度去学习,【下】篇主要是讲解Vue3的高频面试题,开始正文吧!!!

文章目录

    • 一、Vue2 与 Vue3响应式对比
      • 1. Vue2 的 Object.defineProperty
      • 2. Vue.set() 为什么可以解决上述问题?他具体经历了那些步骤你知道吗?
      • 3. 如何实现一个简单的 Vue2 响应式 ?
      • 2. Vue3 的 Proxy
    • 二、Vue3 新特性
        • Composition API
        • 1. 如何理解 setup ?
        • 2. 多根节点
          • 单文件的多根节点
          • 项目的多根节点——多个应用实例
        • 3. reactive() 与 shallowReactive()
        • 4. ref()、isRef() 、toRef()、toRefs()
        • 5. readOnly()、isReadonly() 、shallowReadonly()
        • 6. 生命周期
        • 7. 全局配置
        • 8. 异步组件
        • 9. Teleport
        • 9. 自定义 Hook
          • 举个栗子,自定义一个 Hook 来记录鼠标的位置

一、Vue2 与 Vue3响应式对比

Vue2 与 Vue3 最显著的差别就是响应式的差别,那么是什么原因导致 Vue3 的双向绑定原理采用了Proxy?我们下面来由浅入深的去了解一下。

1. Vue2 的 Object.defineProperty

基础使用:

const initData = { value: 1 };
const data = {};
Object.keys(initData).forEach(key => {
	Object.defineProperty(data, key, {
		get() {
			console.log('访问了', key);
		},
		set(v) {
			console.log('修改了', key);
			data[key] = v;
		}
	})
})

data.value;
data.value = 2;
data;
initData.value2 = 2;
data.value2;

以上就是最基础的使用;
但是我们一起来看一下,下面几个问题会输出什么?

  1. 直接访问 data.value => 访问了 value
  2. 改变 data.value => 修改了 value
  3. 直接输出 data => 空对象: { }
  4. 给 initData 添加一个新值 => 输出新值结果:2
  5. data.value2 又会输出什么? => undefined

总结一下 Vue2 响应式弊端:给对象加属性和删除属性,响应式会检测不到。通常我们是使用 Vue.set( ) 来解决,那么面试官问 Vue.set( ) 为什么可以解决?他具体经历了那些步骤你知道吗?

2. Vue.set() 为什么可以解决上述问题?他具体经历了那些步骤你知道吗?

Vue.set(target, key, value)
// target 必须是一个响应式的数据源,在下面步骤会讲到

会经历一下三个步骤

对 target 进行数据校验
① 数据是 undefined、null或其他基本数据类型,会报错;
② 数据是 数组:则会取出当前数组的长度与当前 key 值的位置进行一个对比,取两者最大值,作为新数组的长度 -> max(target.length, key) ,然后使用 splice(key, 1, value); 当使用 splice 的时候,会自动遍历设置响应式。
③ 数据是 对象:key 是否在对象里,如果在则直接替换;如果不在则直接判断 target 是不是响应式对象;然后判断是不是 Vue 实例或者根的数据对象,如果是 throw error。如果不是直接给 target 的 key 赋值,如果 target 是响应式,使用 defineReactive 将新的属性添加到 target,进行依赖收集;

Vue.set( ) 源码

// example :
this.$set(data, a, 1);
function set(target: Array<any>  object, key: any, val: any): any {
  // isUndef 是判断 target 是不是等于 undefined 或者 nul1
  // isPrimitive 是判断 target 的数据类型是不是 string、number、symbol、boolean 中的一种
  if (process.env.NODE ENV !== 'production' &&(isUndef(target) isPrimitive(target))) {
  	warn(`Cannot set readtive property on undefined, null, or primitive value: $((target: any))`)
  }
  // 数组的处理
  if (Array.isArray(target) && isValidArrayIndex(key)) {
  	target.length = Math .max(target .length, key)
  	target.splice(key,1, val)
	return val
  }

  // 对象,并且该属性原来已存在于对象中,则直接更新
  if (key in target && !(key in object.prototype)) {
	target[key] = val
	return val
  }
  // vue给响应式对象(比如 data 里定义的对象)都加了一个  ob  属性,
  // 如果一个对象有这个 ob属性,那么就说明这个对象是响应式对象,修改对象已有属性的时候就会触发页面渲染
  // 非 data 里定义的就不是响应式对象。
  const ob = (target: any).__ob__

  if (target. isVue  (ob && ob.vmCount)) {
	process.env.NODE ENV !== 'production' && warn(
	'Avoid adding reactive properties to a Vue instance or its root $data' +
	'at runtime - declare it upfront in the data option.!'
	return val
  }

  // 不是响应式对象
  if (!ob) {
	target[key] = val
	return val
  }

  // 是响应式对象,进行依赖收集
  defineReactive(ob.value, key, val)
  // 触发更新视图
  ob.dep.notify()
  return val
}

3. 如何实现一个简单的 Vue2 响应式 ?

export function Vue(options) {
  this.__init(options);
}

// initMixin
Vue.prototype.__init = function (options) {
  this.$options = options;
  // 假如这里是一个字符串,就需要使用 document.querySelector 去获取
  this.$el = options.el;
  this.$data = options.data;
  this.$methods = options.methods;

  // beforeCreate -- initState -- initData
  proxy(this, this.$data);
  // Object.defineProperty
  observer(this.$data);
  new Compiler(this);
};

// this.$data.message ---> this.message
function proxy(target, data) {
  let that = this;
  Object.keys(data).forEach((key) => {
    Object.defineProperty(target, key, {
      enumerable: true,
      configurable: true,
      get() {
        return data[key];
      },
      set(newVal) {
        // 考虑 NaN 的情况
        // this 指向已经改变
        if (!isSameVal(data[key], newVal)) {
          data[key] = newVal;
        }
      },
    });
  });
}

function observer(data) {
  new Observer(data);
}

class Observer {
  constructor(data) {
    this.walk(data);
  }

  walk(data) {
    if (data && typeof data === "object") {
      Object.keys(data).forEach((key) =>
        this.defineReactive(data, key, data[key])
      );
    }
  }

  //要把 data 里面的数据,收集起来
  defineReactive(obj, key, value) {
    let that = this;
    this.walk(value);
    let dep = new Dep();
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        // get时 Dep 收集依赖
        // 4. 对于 num 来说,就要执行这一句
        // 5. num 中的 dep,就有了这个 watcher
        Dep.target && dep.add(Dep.target);
        return value;
      },
      set(newVal) {
        if (!isSameVal(value, newVal)) {
          //赋值进来的新值,是没有响应式的,所以我要在 walk 一次,添加响应式
          value = newVal;
          that.walk(newVal);
          // 重新 set时,notify 通知更新
          // 6.
          dep.notify();
        }
      },
    });
  }
}

// 视图怎么更新?
// 数据改变,视图才会更新。需要去观察
// 1. new Watcher(vm, 'num', ()=>{ 更新视图上的 num 显示 })
class Watcher {
  constructor(vm, key, callback) {
    this.vm = vm; // VUE 的一个实例
    this.key = key;
    this.callback = callback;

    // 2. 此时 Dep.target 作为一个全局变量理解,放的就是就是 watcher
    Dep.target = this;
    // 3. 一旦进行了这一句赋值,是不是就触发了这个值的 getter 函数
    this.__old = vm[key];
    Dep.target = null;
  }

  // 8. 执行所有的 callback 函数
  update() {
    let newVal = this.vm[this.key];
    if (!isSameVal(newVal, this.__old)) this.callback(newVal);
  }
}

// 每一个数据都要有一个 Dep 依赖
class Dep {
  constructor() {
    this.watchers = new Set();
  }
  add(watcher) {
    if (watcher && watcher.update) this.watchers.add(watcher);
  }

  // 7. 让所有的 watcher 执行 update 方法
  notify() {
    this.watchers.forEach((watch) => watch.update());
  }
}

class Compiler {
  constructor(vm) {
    this.vm = vm;
    this.el = vm.$el;
    this.methods = vm.$methods;

    this.compile(vm.$el);
  }
  // 这里是递归编译 #app 下面的所有的节点内容
  compile(el) {
    let childNodes = el.childNodes;
    // childNodes 为类数组
    Array.from(childNodes).forEach((node) => {
      // 判断如果是文本节点
      if (node.nodeType === 3) {
        this.compileText(node);
      }
      // 判断如果是元素节点
      else if (node.nodeType === 1) {
        this.compileElement(node);
      }
      // 判断如果还有子节点,就递归下去
      if (node.childNodes && node.childNodes.length) this.compile(node);
    });
  }

  compileText(node) {
    // 匹配出来 message
    let reg = /\{\{(.+?)\}\}/;
    let value = node.textContent;
    if (reg.test(value)) {
      let key = RegExp.$1.trim();
      // 开始时赋值
      node.textContent = value.replace(reg, this.vm[key]);
      // 给 message 添加观察者
      new Watcher(this.vm, key, (val) => {
        // 数据改变时更新
        node.textContent = val;
      });
    }
  }

  compileElement(node) {
    if (node.attributes.length) {
      Array.from(node.attributes).forEach((attr) => {
        let attrName = attr.name;
        if (attrName.startsWith("v-")) {
          // v- 指定匹配成功,可能是 v-on:click 或者 v-model
          // 假设我们这里就处理两个指令,Vue源码对这一块是有特殊处理的
          attrName =
            attrName.indexOf(":") > -1
              ? attrName.substr(5)
              : attrName.substr(2);
          let key = attr.value;
          this.update(node, key, attrName, this.vm[key]);
        }
      });
    }
  }

  update(node, key, attrName, value) {
    if (attrName === "model") {
      node.value = value;
      new Watcher(this.vm, key, (val) => (node.value = val));
      node.addEventListener("input", () => {
        this.vm[key] = node.value;
      });
    } else if (attrName === "click") {
      node.addEventListener(attrName, this.methods[key].bind(this.vm));
    }
  }
}

function isSameVal(a, b) {
  return a === b || (Number.isNaN(a) && Number.isNaN(b));
}

Vue2 的响应式我们简单介绍一下,下来一起来看 Vue3 的 Proxy!

2. Vue3 的 Proxy

Proxy:代理或拦截器,Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写;Proxy的思路和 React 的 HOC 很像,组件外面包裹一层,对外界的访问进行过滤和改写;

const initData = {value:1};
const proxy = new Proxy(initData, {
  get(target, key, receiver) {
    console.log('访问了', key);
	return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver){
    console.log('修改了', key);
	return Reflect.set(target, key, value, receiver);
  }
})

proxy.value;
proxy.value = 2;
proxy;
proxy.value2 = 2;
proxy.value2;

具体这里就不详细说了,感兴趣的大家可以移步下面链接:
Vue3中的响应式原理,为什么使用Proxy(代理) 与 Reflect(反射)

二、Vue3 新特性

Composition API

composition api : 组合式 api,通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与 < script setup> 搭配使用。这个 setup attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如, < script setup> 中的导入和顶层变量/函数都能够在模板中直接使用。

1. 如何理解 setup ?

通俗一点的讲,setup 可以把他理解为 Vue3 组件模块的入口文件,Vue3 中组件的新特性 ,作为组件统一的入口支持;

未使用 setup 语法糖的写法 (了解即可,实际开发还是使用语法糖写法更加便捷):

setup(props, context){
	context.attrs  -->  this.$attrs
	context.slot  -->  this.$slot
	context.emit  -->  this.$emit

	context.expose
}

使用 setup 语法糖写法

<script setup>
// 变量
const msg = 'Hello!'

// 函数
function log() {
  console.log(msg)
}
</script>

<template>
  <button @click="log">{{ msg }}</button>
</template>

为什么推荐使用 setup 语法糖?

  1. 更少的样板内容,更简洁的代码。
  2. 能够使用纯 TypeScript 声明 props 和自定义事件。
  3. 更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。
  4. 更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。

setup 是在 beforeCreate 和 created 之前去执行

2. 多根节点

什么是多根节点呢?看下图代码

单文件的多根节点

vue3 中之所以可以有多个节点,是因为引入了Fragment的概念,这是一个抽象的节点,如果发现组件有多个根,就创建一个Fragment节点,把多个根节点作为它的children,将来path的时候,如果发现是一个Fragement节点,则直接遍历children创建或更新。

在这里插入图片描述

项目的多根节点——多个应用实例

应用实例并不只限于一个。createApp API 允许你在同一个页面中创建多个共存的 Vue 应用,而且每个应用都拥有自己的用于配置和全局资源的作用域。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

如果你正在使用 Vue 来增强服务端渲染 HTML,并且只想要 Vue 去控制一个大型页面中特殊的一小部分,应避免将一个单独的 Vue 应用实例挂载到整个页面上,而是应该创建多个小的应用实例,将它们分别挂载到所需的元素上去。

3. reactive() 与 shallowReactive()

reactive:通过 proxy 声明一个深层的响应式对象,响应式是深层次的,会影响所有嵌套。 等同于 Vue2 的 Vue.observable()

const person = {
  name: 'Barry',
  age: 18,
  contacts: {
    phone: 1873770
  }
}

const personReactive = reactive(person);
console.log(personReactive); // proxy
const contacts = personReactive.contacts;
console.log(contacts); // proxy

shallowReactive:和 reactive() 不同,这里没有深层级的转换:一个浅层响应式对象里只有根级别的属性是响应式的。

const person = {
  name: 'Barry',
  age: 18,
  contacts: {
    phone: 1873770
  }
}

const personshallowReactive = shallowReactive(person);
console.log(personshallowReactive); // proxy
const contactsShallowReactive = personshallowReactive.contacts
console.log(contactsShallowReactive); // no proxy

4. ref()、isRef() 、toRef()、toRefs()

ref:返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value ,所以我们想要修改 ref 声明的响应式数据需要带上 .value ;
如果将一个对象赋值给 ref, 那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref 它们将被深层地解包。

const count = ref(10);
const cObj = reactive({
  a: 100,
  count
})

console.log(cObj.count);
console.log(cObj.count === count.value);
count.value = 20;
console.log(count.value, cObj.count);
cObj.count = 30;
console.log(count.value, cObj.count);

isRef:检查某个值是否为 ref。

使用 ref 或者 reactive 声明的响应式数据,通过 结构会失去响应式

解决办法:
1. 使用 ref 声明的响应式可以通过 toRef() API
2. 使用 reactive 声明的响应式可以通过 toRefs API

toRef:基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值

const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, 'foo')

// 更改该 ref 会更新源属性
fooRef.value++
console.log(state.foo) // 2

// 更改源属性也会更新该 ref
state.foo++
console.log(fooRef.value) // 3

toRefs:将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

const state = reactive({
  foo: 1,
  bar: 2
})

const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型:{
  foo: Ref<number>,
  bar: Ref<number>
}
*/

// 这个 ref 和源属性已经“链接上了”
state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3

5. readOnly()、isReadonly() 、shallowReadonly()

readOnly:类似于 Object.freeze() 的效果,把一个响应式对象变成一个只读的对象,只读代理是深层的:对任何嵌套属性的访问都将是只读的。会递归的去阻止 Proxy set 的触发,【中】篇会从源码的角度去学习 中间会讲到 readOnly 源码是如何处理的

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // 用来做响应性追踪
  console.log(copy.count)
})

// 更改源属性会触发其依赖的侦听器
original.count++

// 更改该只读副本将会失败,并会得到一个警告
copy.count++ // warning!

isReadonly:检查传入的值是否为只读对象。只读对象的属性可以更改,但他们不能通过传入的对象直接赋值。
通俗一点讲就是检查你传入的值,是否由 readonly 创建出来的

function isReadonly(value: unknown): boolean

shallowReadonly readonly() 的浅层作用形式
这里没有深层级的转换:只有根层级的属性变为了只读。属性的值都会被原样存储和暴露,这也意味着 值为 ref 的属性不会被自动解包了

const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 更改状态自身的属性会失败
state.foo++

// ...但可以更改下层嵌套对象
isReadonly(state.nested) // false

// 这是可以通过的
state.nested.bar++

6. 生命周期

新版的生命周期函数,可以按需导入到组件中,且只能在 setup 函数中使用,但是也可以在 setup 外定义,在 setup 中使用;
setup 是围绕 beforeCreatecreated 生命周期钩子运行的,所以不需要显式的定义它们。换句话说在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

选项式 API组合式 API
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonBeforeUpdate
beforeUnmountonBeforeUnmount
ummountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
activatedonActivated
deactivatedonDeactived

注意,若要在 setup 中引入,需要 vue 中引入对应 hook
官网图片
官网生命周期钩子地址

7. 全局配置

Vue2 中我们通常是使用 Vue.property.xxx = xxx 的方式定义,
第一个弊端 :全局配置很容易意外的污染其他测试用例
第二个弊端 :全局配置使得同一个页面上的多个“应用”在全局配置不同时共享同一个 Vue 副本非常困难;

Vue3 我们是利用: app.config.globalProperties.xxx= xxx 的方式来实现的。通过 Vue 实例上 config 来配置,包含Vue应用程序全局配置的对象,您可以在挂载应用程序之前修改对应的属性;
具体点击右侧链接查看:Vue3中全局配置 axios 的两种方式

可以在应用程序内的任何组件实例中访问的全局属性,组件的属性将具有优先权。同时,可以在组件通过 getCurrentInstance() 来获取全局 globalProperties 中配置的信息, getCurrentInstance 用于获取当前的组件实例,然后通过 ctx 属性获得当前上下文,这样我们就可以在 setup 中使用。

const app = Vue.createApp({});
app.config = {......}
app.config.globalProperties.$htpp = xxx;

app.config.errorhandler = (err, vm, info) => {}

const { ctx } = getCurrentInstance();
ctx.$http

8. 异步组件

异步组件:在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue 提供了 defineAsyncComponent 方法来实现此功能;

全局注册

// 可以利用返回值的实例去自定义异步组件,在那个应用里生效,在那个应用里注册
const AsyncComp = defineAsyncComponent(() => import('./components/AsyncComp.vue'));
app.component('async-comp', AsyncComp)

局部注册

// main.js
const AsyncComp = defineAsyncComponent(() => import('./components/AsyncComp.vue'));
// app.vue
import AsyncComp  from './components/AsyncComp.vue';
{
components: 'async-comp', AsyncComp
}

异步组件的作用:

  1. 打包后不会集成在 index.js 中,会单独进行打包,方便后续操作,可以进行缓存,如多个页面都使用一个相同的组件,可以将打包文件缓存下来;
  2. 如果组件包过大,可以使用 loading 代替显示;

Vue3 支持 suspense,< Suspense > 是一个内置组件,用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成,并可以在等待时渲染一个加载状态。
< Suspense > 组件有两个插槽:#default 和 #fallback。两个插槽都只允许一个直接子节点。在可能的时候都将显示默认槽中的节点。否则将显示后备槽中的节点。
React V16.6.0 中,官方提出了lazy suspense 组件

<Suspense>
  <!-- 具有深层异步依赖的组件 -->
  <Dashboard />

  <!-- 在 #fallback 插槽中显示 “正在加载中” -->
  <template #fallback>
    Loading...
  </template>
</Suspense>

9. Teleport

< Teleport> 是一个内置组件,可以将子节点渲染到存在于父组件以外的 DOM 节点的方案

当处理某些类型的组件(如模式,通知或提示) 时,模板HTML的逻辑可能位于与我们希望染元素的位置不同的文件中
很多时候,与我们的 Vue 应用程序 DOM 完全分开处理时,这些元素的管理要容易得多。所有这些都是因为处理嵌套组件的位置,z-index 和样式可能由于处理其所有父对象的范围而变得棘手。这种情况就是 Teleport 派上用场的地方。我们可以在逻辑所在的组件中编写模板代码,这意味着我们可以使用组件的数据或 props。 但是,然后完全将其渲染到我们Vue应用程序的范围之外。

<button @click="open = true">Open Modal</button>

<Teleport to="body">
  <div v-if="open" class="modal">
    <p>Hello from the modal!</p>
    <button @click="open = false">Close</button>
  </div>
</Teleport>

9. 自定义 Hook

Vue3 的 hooks 其实可以参考 React 的自定义 hooks 的定义,在 React 中,在函数组件中保留 state 数据的同时,融入生命周期函数,将组件整体作为一个钩子函数。
当组件复杂时,多个组件中一些重复的逻辑可以被抽象出来。在 Hook 诞生之前,React 和 Vue 都拥有高阶组件的设计模式,在 React 使用到 HOC,在 Vue 2 中使用到 mixin。为什么要舍弃它们而使用 Hook,使用自定义 Hook又有哪些优点,我们先简单了解一下 HOC 和 mixin ,对比后便知。

在这里插入图片描述
HOC 的原理是把组件作为参数传入一个函数,加入复用部分后将新的组件作为返回值,使用了装饰器模式mixin 像是把复用的部分拆解成一个个小零件,某个组件需要时就拼接进去
在实践中,mixin 有如下缺点:
1.引入了隐式依赖关系。
2不同 mixins之间可能会有先后顺序甚至代码冲突覆盖的问题
3.mixin 代码会导致滚雪球式的复杂性
4多个 mixin 导致合并项不明来源
为了避开这些问题,React 采用 HOC,但它依然存在缺陷
1.一个组件的state影响许多组件的props
2.造成地狱嵌套
不过使用全新的 Hook 组件结构,可以实现平铺式调用组件的复用部分,解决了 mixin 的来源不明和 HOC 的地狱嵌套问题。

举个栗子,自定义一个 Hook 来记录鼠标的位置

Tips: 一般我们的自定义 Hook 都需要使用 use 开头!

// src/hooks/useMousePosition. ts
import { ref, onMounted, onUnmounted, Ref } from 'vue
function useMousePosition() {
  const x = ref(0)
  const y = ref(0)
  const updateMouse = (e) => {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => {
    document.addEventListener('click', updateMouse)
  })
  onUnmounted(() => {
    document.removeEventListener('click', updateMouse)
  })
  return { x, y }
}
export default useMousePosition

<template>
  <div>
	<p>X: {{ x }}</p>
	<p>Y: {{ y }}</p> I
  </div>
</template>
<script lang="ts">
import { defineComponent} from 'vue'
//引入hooks
import useMousePosition from ' ../ ../hooks/useMousePosition'
export default defineComponent({
  setup () {
    //使用hooks功能
    const { x, y} = useMousePosition()
    return {
      X,
    }
  }
})
</script>



结语:【Vue3 核心模块源码解析(上)】到此结束,此篇还是以Vue2的部分回顾,加上Vue3的新特性的基础使用,以及部分见解,有不对的地方欢迎大家及时指出,本文到此结束!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/373522.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【密码学】 一篇文章讲透数字证书

【密码学】 一篇文章讲透数字证书 数字证书介绍 数字证书是一种用于认证网络通信中参与者身份和加密通信的证书&#xff0c;人们可以在网上用它来识别对方的身份。 我们在上一篇博客中介绍了数字签名的作用和原理&#xff0c;数字签名可以防止消息被否认。有了公钥算法和数字签…

史上最全面的软件测试面试题总结(接口、自动化、性能全都有)

目录 思维发散 Linux 测试概念和模型 测试计划与工具 测试用例设计 Web项目 Python基础 算法 逻辑 接口测试 性能测试 总结感谢每一个认真阅读我文章的人&#xff01;&#xff01;&#xff01; 重点&#xff1a;配套学习资料和视频教学 思维发散 一个球&#xff…

二叉树——二叉搜索树的最小绝对差

二叉搜索树的最小绝对差 链接 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 示例 1&#xff1a; 输入&#xff1a;root [4,2,6,1,3] 输出&#xff1a;1 示例 2&…

PowerDesigned16连接Oracle出现“Could not initialize JavaVM“时的解决步骤

PowerDesigned需要连接到数据库&#xff0c;我使用的是oracle&#xff0c;但总是连接不上&#xff0c;输出栏提示"Could not initialize JavaVM"。 经过查找资料&#xff0c;发现是PowerDesigned16是32位的&#xff0c;只能使用32位的JDK来运行JDBC驱动&#xff0c;…

如何从零开始系统的学习项目管理?

经常会有人问&#xff0c;项目管理到底应该学习一些什么&#xff1f;学习考证之后能得到什么价值&#xff1f; 以下我就总结一下内容 一&#xff0c;学习项目管理有用吗&#xff1f; 有效的项目管理带来的益处大致包括以下几个方面&#xff1a;更有效达成业务目标、满足相关…

人工智能轨道交通行业周刊-第35期(2023.2.20-2.26)

本期关键词&#xff1a;重庆智慧轨道、智能运维主机、标准轨距、地方铁路公报、景深、机器视觉应用 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通Rai…

第12天-商品维护(发布商品、商品管理、SPU管理)

1.发布商品流程 发布商品分为5个步骤&#xff1a; 基本信息规格参数销售属性SKU信息保存完成 2.发布商品-基本信息 2.1.会员等级-会员服务 2.1.1.会员服务-网关配置 在网关增加会员服务的路由配置 - id: member_routeuri: lb://gmall-memberpredicates:- Path/api/member/…

学习python第一天---前缀和

一、3956.截断数组&#xff08;前缀和&#xff09;二、前缀和&#xff08;前缀和&#xff09;[0]list(map(int,input().split()))三、子矩阵的和&#xff08;前缀和&#xff09;range(1,n1)四、K倍区间&#xff08;前缀和&#xff09;五、激光炸弹&#xff08;前缀和&#xff0…

模型部署笔记

目录模型部署工作ONNX存在的意义ONNX&#xff08;Open Neural Network Exchange&#xff09;ONNX示例模型推理示例Batch调整量化量化方式常见问题模型部署工作 训练好的模型在特定软硬件平台下推理针对硬件优化和加速的推理代码 训练设备平台&#xff1a; CPU、GPU、DSP ONN…

2023.02.26 学习周报

文章目录摘要文献阅读1.题目2.摘要3.介绍4.模型4.1 SESSION-PARALLEL MINI-BATCHES4.2 SAMPLING ON THE OUTPUT4.3 RANKING LOSS5.实验5.1 数据集5.2 验证方式5.3 baselines5.4 实验结果6.结论深度学习元胞自动机1.定义2.构成3.特性4.思想5.统计特征流形学习1.降维2.空间3.距离…

一些硬件学习的注意事项与快捷方法

xilinx系列软件 系统适用版本 要安装在Ubuntu系统的话&#xff0c;要注意提前看好软件适用的版本&#xff0c;不要随便安好了Ubuntu系统又发现对应版本的xilinx软件不支持。 如下图&#xff0c;发行说明中会说明这个版本的软件所适配的系统版本。 下载 vivado vitis这些都可以…

IT男的一次中年破局尝试--出书

一、转战外企 接上回《人到中年——IT男择业感悟》后&#xff0c;自己从大央企去了某知名外企。外企虽然最近几年的日子已经没有10年前的辉煌与滋润&#xff0c;但相对来说&#xff0c;还能勉强找到工作与生活的平衡点。 划重点&#xff0c;35岁上下的人换工作理由&#xf…

SpringBoot+React博客论坛系统 附带详细运行指导视频

文章目录一、项目演示二、项目介绍三、项目运行截图四、主要代码一、项目演示 项目演示地址&#xff1a; 视频地址 二、项目介绍 项目描述&#xff1a;这是一个基于SpringBootReact框架开发的博客论坛系统。首先&#xff0c;这是一个前后端分离的项目&#xff0c;文章编辑器…

大学物理期末大题专题训练总结-磁学大题

&#xff08;事先声明指的是简单的那个磁学大题&#xff0c;另外一类涉及储存的磁能、磁感应强度分布下次说&#xff09;求个磁通量&#xff0c;求个感应电动势&#xff0c;求个安培力大小......这个感觉是不是像你梦回高中&#xff1f;当然&#xff0c;这一块大题跟高中磁学部…

hadoop-Combiner合并、OutputFormat

一、Combiner合并 Combiner是MR程序中Mapper和Reducer之外的一种组件。 2&#xff09;Combiner组件的父类就是Reducer 3&#xff09;Combiner和Reducer的区别在与运行的位置&#xff1b;Combiner是在每一个MapTask所在的节点运行&#xff1b;Reducer是接收全局所有Mapper的输出…

c++11 标准模板(STL)(std::unordered_set)(九)

定义于头文件 <unordered_set>template< class Key, class Hash std::hash<Key>, class KeyEqual std::equal_to<Key>, class Allocator std::allocator<Key> > class unordered_set;(1)(C11 起)namespace pmr { templat…

Linux学习(8.5)文件内容查阅

目录 文件内容查阅&#xff1a; 直接检视文件内容 cat (concatenate) tac (反向列示) nl (添加行号列印) 可翻页检视 more (一页一页翻动) less (一页一页翻动) 数据撷取 tail (取出后面几行) 非纯文字档&#xff1a; od 修改文件时间或建置新档&#xff1a; touc…

数据结构(六)二叉树

一、树形结构概念树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。它具有以下的特点&#xff1a;1、有一个…

昇腾AI新技能,还能预防猪生病?

国药集团动物保健股份有限公司&#xff08;简称“国药动保”&#xff09;是专业从事动物保健产品研发、生产和销售的国家高新技术企业&#xff0c;是国内少数几家具备新产品原创能力的动物保健企业。其中&#xff0c;猪圆环病毒灭活疫苗等市场份额位居行业前列。 “猪圆环病毒…

【Linux学习笔记】8.Linux yum 命令和apt 命令

前言 本章介绍Linux的yum命令和apt命令。 Linux yum 命令 yum&#xff08; Yellow dog Updater, Modified&#xff09;是一个在 Fedora 和 RedHat 以及 SUSE 中的 Shell 前端软件包管理器。 基于 RPM 包管理&#xff0c;能够从指定的服务器自动下载 RPM 包并且安装&#xf…