reactive对象的简单实现
主要通过reactive.spec.ts这个测试案例来实现功能
import { reactive } from "../reactive"
describe('reactive',()=>{
it('happy path',()=>{
const original = {foo:1}
const observed = reactive(original)
expect(observed).not.toBe(original)
expect(observed.foo).toBe(1)
})
})
reactive是通过Proxy对传入对象进行了一层封装,然后通过get,set对传入对象进行进行一系列操作,如依赖收集,触发依赖等。值得注意的是他们的值也同时通过Reflect的get和set来进行了获取和设置。
代码如下:
import { track, trigger } from "./effect"
export function reactive(raw){
return new Proxy(raw,{
get(target,key){
const res = Reflect.get(target,key)
track(target,key)
return res
},
set(target,key,value){
const res = Reflect.set(target,key,value)
trigger(target,key)
return res
}
})
}
下面我也将对track依赖收集和trigger触发依赖进行详细讲解
reactive的依赖收集和触发依赖
主要通过effect.spec.ts这个测试案例来实现功能
import { reactive } from "../reactive"
import { effect } from "../effect"
describe('effect',()=>{
it('happy path',()=>{
const user = reactive({
age:10
})
let nextAge
effect(()=>{
nextAge = user.age+1
})
expect(nextAge).toBe(11)
user.age++
expect(nextAge).toBe(12)
})
})
其实主要user.age++后的这个expect的通过就需要到对依赖的收集和触发
依赖收集
依赖收集主要在Proxy中的get函数中通过track实现,而我们选择在effect.ts中导出track函数,我个人觉得effect中也比较好处理,对于effect实例这些变量的获取上都是比较方便的。
依赖收集中最重要的其实就是看明白这个关系: target -> key -> dep,以及理解targetMap,depsMap,dp这三个变量的关系,如果这两个理解了就不会太难了。
首先附上一张图依赖收集和触发依赖的一张图,有助于理解这些关系和他们整个流程:
由图可见,dep中存储的依赖就是effect中的回调函数fn。(因此为了fn不会重复加入,所以使用了set集合)
接下来就解释一下这几个变量的意思:
首先target -> key -> dep链条的就是他们层层的依赖关系
targetMap:以target为键depsMap为值的Map集合
depsMap::以key为键dep为值的Map集合
dep:是存储fn的set集合
存储关系如图所示:
依赖收集的代码如下:
const targetMap = new Map()
export function track(target,key){
// target -> key -> dep
let depsMap = targetMap.get(target)
if(!depsMap){
depsMap = new Map()
// 没有depsMap时要加进去
targetMap.set(target,depsMap)
}
let dep = depsMap.get(key)
if(!dep){
dep = new Set()
depsMap.set(key,dep)
}
dep.add(activeEffect)
}
接下来就说下activeEffect是哪里来的,接下来先附上ReactiveEffect类的代码
let activeEffect
class ReactiveEffect {
private _fn: any
constructor(fn){
this._fn = fn
}
run(){
// 依赖收集之前去给激活的effect赋值
activeEffect = this
this._fn()
}
}
这段代码就可以看出activeEffect是effect.ts中声明的一个全局变量,然后当运行run函数的时候给他赋值为当前的激活的effect。
触发依赖
依赖收集做好了,起触发依赖就很简单了,就跟着上面的存储关系图,用targetMap找到dep容器,然后循环调用里面effect中run方法就可以了。
代码如下:
export function trigger(target,key){
const depsMap = targetMap.get(target)
const dep = depsMap.get(key)
for (const effect of dep) {
effect.run()
}
}