前言
vue三大核心:组件,虚拟dom,响应式,那这里我们就来深入理解vue的响应式原理,并手写简单的响应式系统,让我们对vue的理解上升一个level。
先回顾一下ref和reactive
-
ref
: 用于创建一个响应式的引用对象。ref
通常用于包装基本类型的值(如number
、string
等),使其成为响应式对象。ref
返回的对象包含一个value
属性,通过响应式系统[proxy(vue3)或Object.defineProperty(vue2)]
监听.vule
属性的访问或修改,触发自动更新。javascript
csharpimport { ref } from 'vue'; const count = ref(0); console.log(count.value); // 0 count.value++; // 响应式更新
-
reactive
: 用于创建一个响应式的对象。reactive
可以接收一个普通对象,并返回该对象的响应式代理。与ref
不同,reactive
直接操作对象本身,而不是通过value
属性。javascript
javascriptimport { reactive } from 'vue'; const state = reactive({ count: 0, }); console.log(state.count); // 0 state.count++; // 响应式更新
1. Vue 3 响应式原理概述
Vue 3 的响应式系统主要依赖于 Proxy
和 Reflect
这两个 ES6 特性。Proxy
可以拦截对象的读取和修改操作,而 Reflect
则提供了一组操作对象的方法,与 Proxy
配合使用可以实现对对象的深度监听。说人话就是:利用ES6的proxy
数据代理,通过reactive()
函数,给普通对象包裹一层proxy
,通过代理对象来操作这个普通对象。先了解一下reactive()
函数
reactive()
函数的完整参数结构
javascript
function reactive(target, handler) {
// 实现代码
}
-
target
:必需参数,要转换为响应式的对象。 -
handler
:可选参数,包含拦截方法的配置对象,用于自定义响应式行为。 -
默认行为:若不传递handler,Vue使用内置的mutableHandlers,包含基础的get/set拦截逻辑
-
自定义覆盖:传递handler可覆盖默认方法(这里就不多讲),我们重点讲一下默认行为
mutableHandlers
-
vue3源码当中的
mutableHandlers
(来自baseHandlers.ts定义)
js
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
};
这里我们关注get和set两个拦截器
举个例子:
.vue
js
const target = { num1:2,num2:2}
const counter = reactive(target)
console.log(counter.num1)//读取:触发get拦截器
counter.num1 = 100;// 修改:触发set拦截器
如上面代码,普通对象(也叫目标对象叫他
target
):{num1:1,num2:2}
被代理之后返回一个新的proxy
对象:响应式对象,这里是counter
,它代理了对target
的所有操作。reactive()
通过Proxy
实现响应式,通过mutableHandlers
(里面的get和set)劫持对象属性的读写操作。这里还不能很好理解get和set的作用,先不急看下文。
javascript
// vue 可以在后端运行
// vue 也是模块化写出来的 reactivity模块
const {
// 从 @vue/reactivity 模块中导入 effect 函数,
// 用于创建响应式副作用,当依赖的响应式数据变化时会重新执行
effect,
reactive // vue 响应式
} = require("@vue/reactivity");
// 不用挂载到页面上, node 下运行
let dummy
// 响应式
const counter = reactive({
num1:1,
num2:2,
})
// effect 是一个函数, 里面的代码会立即执行
// 接受一个函数作为参数
// 自动更新的是 函数, {{}} computed 生命周期......
effect( () =>{
// proxy get 收集依赖
dummy = counter.num1 + counter.num2
console.log(dummy,'依赖被执行1');
})
effect( () =>{
// 收集依赖
console.log(dummy,'依赖被执行2');
})
setInterval(() => {
// proxy set 触发更新
counter.num1++
},1000)
setInterval(() =>{
// 触发更新
counter.num2++
},5000)
以上代码:
get
拦截器负责在访问属性时收集依赖,也就是把依赖该属性的effect
函数记录下来。set
拦截器负责在属性值改变时触发更新,也就是让所有依赖该属性的effect
函数重新执行。- 简而言之:
get
用于收集依赖,set
用于触发已收集的依赖(这里是effect
函数)重新执行。 - effect:在Vue的响应式系统里,
effect
函数是核心组成部分,它的主要功能是创建响应式副作用。当依赖的响应式数据发生变化时,副作用函数会重新执行。(假设我有一个需求希望在{ num1:1, num2:2, }的属性值发生变化时输出相应的值和特定语句,那本例当中就通过响应式数据和effect完成了这个需求)。
运行一下 基于上面我们对于响应式原理有了一定的理解,那下面这张图将进一步加深我们的理解。
上图(所谓一图胜千言)
哇哦哇哦,这就很到位了,图中普通对象通过reactive()
函数包裹一层proxy,当属性被读取时候(counter.num1+counter.num2
),触发get,调用get的track方法,把efffect注入到依赖地图。当属性被修改时触发set,调用set的trigger方法把之前收集到并注入的efffect挨个执行。当然efffect只是依赖的一种,在实际应用当中有各种依赖比如:
- 数据绑定:在前端开发中,经常需要将数据绑定到 DOM 元素上。例如,在 Vue 中,当一个组件的数据发生变化时,与之绑定的 DOM 元素会自动更新。
- 计算属性:计算属性是根据其他数据计算得出的属性。当依赖的数据发生变化时,计算属性会自动重新计算 到这一步
那到这一步呢?对于响应式原理的理解就有十之八九了(当然,对于基本理解而言),下一章咱们直接撸源码,到时候面试被问到,就是你我表演的时刻。觉得有用就点个关注吧,会有更多源码解析题哟!