下面是手写 Vue3 响应式系统的完整攻略。
1. 概述
Vue3 的响应式系统使用了 Proxy 对象来监测对象的变化,相较于 Vue2 的响应式系统使用 Object.defineProperty 进行数据劫持,Proxy 具有更好的性能和更简洁的 API。
当我们修改 Vue3 中的 reactive 对象内部的数据时,就会触发依赖收集和派发更新的操作,这样就能保证视图的自动更新。
Vue3 响应式系统的核心可以归纳为一个数据结构:Reactive Object,它包含了 target、handlers、depsMap 三个部分。
- target:需要被监测的对象。
- handlers:代理处理器,用于拦截对 Reactive Object 的操作。
- depsMap:依赖收集器,用于存储 Reactive Object 内部所有属性的依赖关系。
2. 实现步骤
2.1 创建 Reactive Object
首先,我们需要编写 createReactiveObj 函数,用于创建一个响应式对象。
function createReactiveObj(target) {
const handlers = {
get(target, key, receiver) {
// TODO: 依赖收集
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
// TODO: 派发更新
return Reflect.set(target, key, value, receiver)
}
}
const reactiveObj = new Proxy(target, handlers)
return reactiveObj
}
这里我们使用 Proxy 对象创建了一个代理处理器 handlers,其中 get 方法用于实现依赖收集,set 方法用于实现派发更新。
2.2 实现依赖收集
在 get 方法中,我们需要实现依赖收集的操作,即将属性和对应的依赖存储到 depsMap 中。
//... 在createReactiveObj函数中省略部分代码
function createReactiveObj(target) {
//... 省略部分代码
const depsMap = new Map()
const handlers = {
get(target, key, receiver) {
// 依赖收集:如果当前存在 activeEffect,将其加入依赖列表
if (activeEffect) {
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
dep.add(activeEffect)
}
return Reflect.get(target, key, receiver)
},
//... 省略部分代码
}
const reactiveObj = new Proxy(target, handlers)
return reactiveObj
}
这里我们使用 Map 和 Set 来存储依赖关系,将 target 对象的属性作为键,将对应的依赖函数作为值。
2.3 实现派发更新
在 set 方法中,我们需要实现派发更新的操作,即通知 depsMap 中存储的所有依赖函数执行。
//... 在createReactiveObj函数中省略部分代码
let activeEffect = null
function createReactiveObj(target) {
//... 省略部分代码
const depsMap = new Map()
const handlers = {
//... 省略部分代码
set(target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver)
const result = Reflect.set(target, key, value, receiver)
if (result && oldValue !== value) {
// 派发更新:执行依赖函数
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}
return result
}
}
const reactiveObj = new Proxy(target, handlers)
return reactiveObj
}
这里我们使用 activeEffect 变量来存储当前的依赖函数,将其加入依赖列表,然后在 set 方法中执行存储的依赖函数即可实现派发更新。
2.4 创建依赖函数
最后,我们需要编写 effect 函数,用于创建一个依赖函数。
function effect(fn) {
activeEffect = fn
fn()
activeEffect = null
}
这里我们使用 activeEffect 变量来存储当前的依赖函数,执行依赖函数时,将其置为 null,以避免不必要的影响。
3. 示例说明
下面,我们通过两个例子演示如何使用手写 Vue3 响应式系统。
3.1 基本使用
const state = { count: 0 }
const reactiveState = createReactiveObj(state)
effect(() => console.log(reactiveState.count))
reactiveState.count += 1 // 控制台输出:1
reactiveState.count += 1 // 控制台输出:2
这里我们创建了一个响应式对象 reactiveState,将其赋值为 state 对象的代理对象。
然后,我们使用 effect 函数创建一个依赖函数,每次执行时打印 reactiveState.count 的值。
接着,我们对 reactiveState.count 进行了两次修改,会发现控制台依次输出了 1 和 2,即每次修改都触发了依赖函数的执行。
3.2 嵌套使用
const state = { count: 0, nested: { name: 'vue3' } }
const reactiveState = createReactiveObj(state)
effect(() => {
console.log(reactiveState.count, reactiveState.nested.name)
})
reactiveState.nested.name = 'reactive' // 控制台输出:0 'reactive'
reactiveState.count += 1 // 控制台输出:1 'reactive'
这里我们创建了一个嵌套对象 state,其中包含了一个 count 属性和一个嵌套的对象 nested,包含一个 name 属性。
然后,我们使用 createReactiveObj 函数创建一个响应式对象 reactiveState。
接着,我们使用 effect 函数创建一个依赖函数,每次执行时打印 reactiveState.count 和 reactiveState.nested.name 的值。
最后,我们对 reactiveState.nested.name 和 reactiveState.count 进行了修改,会发现控制台依次输出了 0 'reactive' 和 1 'reactive',即每次修改都触发了依赖函数的执行,包括嵌套对象的属性。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:手写 Vue3 响应式系统(核心就一个数据结构) - Python技术站