4、带你一步步实现vue3源码之依赖收集&触发依赖
依赖收集&触发依赖
在此之前,我们已经实现了reactive
和effect
,并且留了一个全局变量activeEffect
,我们一起来思考一下,这些东西有什么用,又是怎么联系在一起的。
分析
reactive(obj)
返回的是一个proxy
代理对象,当程序执行到obj.xx
的时候会触发代理对象的get
请求,当执行obj.xx = xx
的时候则会触发代理对象的set
请求
effect(fn)
返回的是一个ReactiveEffect
实例对象,在函数执行的时候给activeEffect
赋值为当前的实例对象,而实例对象存储的有用户传入的fn
,并且可通过内部的run
方法来执行这个fn
思考
通过上面的准备工作,我们是否可以这样做?当我们在effect
函数内部触发obj
的get请求的时候,我们去把effect(fn)
这个函数存起来,当程序触发它的set
请求的时候,我们再从我们存储的变量中取出来,挨个去执行实例对象中的run
函数,那不就相当于,当数据更新的时候,会自动去执行effect(fn)
中的fn
吗?
单测
下面我们再来完善effect
的单测,加上更新机制
describe("effect", () => {
it("happy path", () => {
...
user.age++
expect(nextAge).toBe(12)
})
})
当我们的响应式对象其中的某个属性值改变的时候,我们希望nextAge
也会同步改变。
编码
我们一步步来实现依赖收集和触发依赖的功能
// src/reactivity/reactive.ts
import {track, trigger} from './effect'
export function reactive (raw) {
return new Proxy(raw, {
get (target, key) {
...
track(target, key)
...
},
set (target, key, value) {
...
trigger(target, key)
...
}
})
}
上面的track
函数便是我们用来收集依赖,trigger
用来触发依赖,这两个函数我们都是写在effect.ts
文件中。
依赖收集
单纯的说依赖收集,但是到底依赖是什么,我们到底要收集什么,又该怎么存储,请大家仔细观摩下面的图。
我们实际要存储的数据结构便是target
->key
->effect
。
const targetMap = new Map()
export function track (target, key) {
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
dep.add(activeEffect)
}
在上述的实现代码中,我们通过一个全局变量targetMap
来存储我们收集的所有依赖,数据格式是Map
,然后依次从中读取target.key
的存储容器dep
,然后将activeEffect
存进去,这样我们就将所有的fn
存储起来。
触发依赖
上面,我们已经收集好了所有的依赖,触发依赖就显得简单多了。
export function trigger (target, key) {
const depsMap = targetMap.get(target)
const dep = depsMap.get(key)
dep.forEach((effect) => {
effect.run()
})
}
根据trigger(target, key)
传入的值,我们从targetMap
中取出对应的所有effect
实例对象,然后执行所有实例的run
方法,这样便是我们的触发依赖。
测试结果
PS D:\user\desktop\mini-vue> yarn test
yarn run v1.22.10
$ jest
PASS src/reactivity/tests/effect.spec.ts
PASS src/reactivity/tests/index.spec.ts
PASS src/reactivity/tests/reactive.spec.ts
Test Suites: 3 passed, 3 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 1.108 s
Ran all test suites.
Done in 2.36s.
总结
其实vue3
的响应式最根本的功能我们已经基本完成,也能达到数据跟随改变了,以前没了解的时候总觉得很神奇,现在看看其实也就这么回事,编码思路真的太重要,哪怕你懂所有的语法api可最后不一定能写出如此精妙的代码,好好学习吧,骚年们!
下一节,我们将来完善effect
的runner
功能。
转载自:https://juejin.cn/post/7067841475203203109