baseHandler 中就是对数据的获取及修改进行拦截,并通过 Reflect 执行 get/set 的原本操作,并在获取值为 Object 时递归进行响应式处理。很简单地就完成了数据的响应式处理。
依赖收集
依赖收集与 Vue2 类似,在 getter 中收集依赖,setter 中触发依赖,修改 baseHandler 如下:
const baseHandler = { get(target, key) { const res = Reflect.get(target, key) // 收集依赖 track(target, key) return typeof res == 'object' ? reactive(res) : res }, set(target, key, val) { const info = { oldValue: target[key], newValue: val } const res = Reflect.set(target, key, val) // 触发更新 trigger(target, key, info) return res } }
track 函数收集依赖,trigger 函数触发依赖更新。
首先需要两个全局变量,用于保存当前待收集的依赖对象的 effectStack 及一个记录所有数据及其对应依赖的表 targetMap 。
const effectStack = [] const targetMap = new WeakMap()
接下来定义这收集依赖及触发依赖更新这两个函数:
function track(target, key) { // 从栈中拿到待收集的依赖对象 let effect = effectStack[effectStack.length - 1] if (effect) { // 通过 target 及 key 从依赖映射表中拿到对应的依赖列表(Set类型) // 首次需要对依赖映射表初始化 let depsMap = targetMap.get(target) if (depsMap === undefined) { depsMap = new Map() targetMap.set(target, depsMap) } let dep = depsMap.get(key) if (dep === undefined) { dep = new Set() depsMap.set(key, dep) } // 若 target.key 对应的依赖列表中不存在该依赖则收集 if (!dep.has(effect)) { dep.add(effect) } } }
function trigger(target, key, info) { // 依赖映射表中取出 target 相关数据 const depsMap = targetMap.get(target) if (depsMap === undefined) { return } // 普通依赖对象的列表 const effects = new Set() // 计算属性依赖对象的列表 const computedRunners = new Set() if (key) { // 取出 key 相关的依赖列表遍历分类存入 effects 及 computedRunners let deps = depsMap.get(key) deps.forEach(effect => { if (effect.computed) { computedRunners.add(effect) } else { effects.add(effect) } }) } // 遍历执行所有依赖对象 const run = effect=> effect() effects.forEach(run) computedRunners.forEach(run) }
track 及 trigger 的大致代码也很简单,track 是拿到待收集的依赖对象 effect 后收集到 effectStack,trigger 是从 effectStack 拿到对应的依赖列表遍历执行。
到现在就差这个依赖对象了,根据上面 trigger 函数可以知道,这个依赖 effect 首先是个函数可以执行,并且还有自身属性,如 computed 表示其为一个计算属性的依赖,有时会根据该标识进行写特殊处理。
下面开始介绍这个依赖对象是如何产生的:
// 创建依赖对象 function createReactiveEffect(fn, options) { const effect = function effect(...args) { return run(effect, fn, args) } effect.computed = options.computed effect.lazy = options.lazy return effect } function run(effect, fn, args) { if (!effectStack.includes(effect)) { try { effectStack.push(effect) return fn(...args) } finally { effectStack.pop() } } }
createReactiveEffect 是一个高阶函数,内部创建了一个名为 effect 的函数,函数内部返回的是一个 run 函数,run 函数中将依赖 effect 对象存入全局的待收集依赖栈 effectStack 中,并执行传入的回调函数,该回调函数其实就是一开始示例中 effect 函数传入的修改 Dom 的函数。也就是说依赖对象作为函数直接执行就会添加依赖到全局栈并执行回调函数。
回调函数中如果有读取了响应式数据的话则会触发 proxy 的 get 收集依赖,这时就能从 effectStack 上拿到该依赖对象了。
然后给 effect 增加了 computed lazy 属性后返回。
最后就是对外暴露的 effect 及 computed 函数了:
// 创建依赖对象并判断非计算属性则立即执行 function effect(fn, options = {}) { let e = createReactiveEffect(fn, options) if (!options.lazy) { e() } return e } // computed 内部调用 effect 并添加计算属性相关的 options function computed(fn) { const runner = effect(fn, { computed: true, lazy: true }) return { effect: runner, get value() { return runner() } } }
computed 就不多说了,effect 就是将传入的回调函数传给 createReactiveEffect 创建依赖对象,然后执行依赖对象就会执行回调函数并收集该依赖对象。
总结