Vue3 瞎搞第一蛋:初步实现effect和reactive的关系
我们实现reactivity使用TDD的思想,即实现每个功能之前先写一个测试,然后是测试从失败到成功来实现功能.
- 我们知道
Vue3如何实现响应式的, 使用reactive实现对对象数据的代理,然后通过effect在响应式数据发生变化的时候,依赖响应式数据的函数再次执行
-
新建
reactive.spec.tsdescribe("reactive", () => { it("happy path", () => { const origin = { foo: "bar" }; const obj = reactive(origin); expect(origin).not.toBe(obj); expect(obj.foo).toBe("bar"); }); }); -
新建
reactive.ts实现reactive来通过上述测试export function reactive(target) { return new Proxy(target, { get(target, key) { const res = Reflect.get(target, key, receiver); return res; }, set(target, key, value) { const res = Reflect.set(target, key, value, receiver); return res; }, }); }
-
实现
effect功能,在effect内部使用响应式数据,当响应式数据发生变化的时候需要触发这个函数再次执行-
新建
effect.spec.ts文件describe("effect", () => { it("happy path", () => { const obj = reactive({ foo: 1 }); const fn = jest.fn(() => obj.foo); effect(fn); expect(fn).toHaveBeenCalledTimes(1); obj.foo = 2; expect(fn).toHaveBeenCalledTimes(2); }); }); -
新建
effect.ts文件来通过上述测试 上述问题我们应该产生一下疑问effect内部函数怎么和响应式数据obj建立联系?- 当响应式数据
obj发生变化的怎么通过他们之间的联系触发这个函数?
上面两个问题我们需要一个联系
container,当我们在effect函数中读取数据的时候会触发get,然后将这个函数放进container中,然后在set的时候将container中的这个函数拿出来执行。// reactive.ts import { track, trigger } from "./effect"; export function reactive(target) { return new Proxy(target, { get(target, key) { const res = Reflect.get(target, key, receiver); // TODO: track track(target, key); return res; }, set(target, key, value) { const res = Reflect.set(target, key, value, receiver); // TODO: trigger 触发依赖 trigger(target, key); return res; }, }); }const targetMap = new WeakMap(); let activeEffect; // 收集依赖 export function track(target, key) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let deps = depsMap.get(key); if (!deps) { depsMap.set(key, (deps = new Set())); } deps.add(activeEffect); } // 触发依赖 export function trigger(target, key) { let depsMap = targetMap.get(target); if (!depsMap) return; let deps = depsMap.get(key); if (!deps) return; deps.forEach((fn) => { fn(); }); } export function effect(fn) { activeEffect = fn; fn(); }
-
以上就是实现了一个暂时简版reactive和effect一起作用的例子
-
技术总结
effect需要和reactive代理过的对象形成一个映射关系?那么怎么形成这个映射关系,我们知道在reactive中代理的对象,当我们进行取值的时候会触发get函数, 我们赋值新值的时候就会触发set函数。那么我们就可以在get的时候,将这个函数放在一个桶中,然后在set的时候从这个桶中拿出来执行. 大体的关系如下:- 如果有一个
key
target - key - effectFn1 - effectFn2- 如果有多个
key
target - key1 - effectKey1Fn1 - effectKey1Fn2 - key2 - effectKey2Fn1 - effectKey2Fn2我们这个
桶为什么用WeakMap,是因为WeakMap对key是弱引用,不会影响GC过程。即当对应的key所引用的对象不存在时,说明用户不需要他了,这时候垃圾回收器会完成回收任务. - 如果有一个