mini-vue 从0到1
我们使用TDD开发mini-vue
我们先看 effect.spec.ts 新建effect.spec.ts
it("reactive", () => {
const user = reactive({ age: 10 });
let nextage;
effect(() => {
nextage = user.age + 1;
});
expect(nextage).toBe(11);
user.age += 1;
expect(nextage).toBe(12);
});
1. 我们首先新建一个user 响应式对象
2. 然后我们通过一个effect 来收集 我们响应式的依赖 effect里面接受一个 fn函数
3. 初始化的时候我们会去调用这个 fn 函数 第一次 初始化就会执行这个fn函数
4. 我们的 fn 函数 里面 调用了 user.age 这样就会触发我们get操作 我们的get操作就是要去收集这个依赖
5. user 这个响应式 对象就会把 这个fn 给收集起来 当我们去修改了user.age 的值的时候我们触发了set操作 他会把之前我们所有get 收集的依赖 (收集的依赖就是这个fn)然后去调用这个 依赖 就是调用这个fn
我们先实现reactive,然后我们在看 reactive 的测试 新建reactive.spec.ts
describe("reactive", () => {
it("happy path", () => {
const orginal = { foo: 1 };
const obversed = reactive(orginal);
expect(obversed.foo).toBe(1);
expect(obversed).not.toBe(orginal);
expect(isReactive(obversed)).toBe(true);
expect(isProxy(obversed)).toBe(true);
});
})
然后我们实现 reactive 我们新建reactive.ts
export function reactive(raw) {
return new Proxy(raw, {
get(target, key){
const res = Reflect.get(target, key)
return res
},
set(target,key,value){
const res = Reflect.set(target,key,value)
return res
}
})
}
这时候我们验证 reactive 中的测试 就可以执行了 接下来~~
我们 proxy 代理 reactive返回的对象 所以我们只需要在 get收集effect的fn 然后在 set的时候触发fn
class ReactiveEffect {
private _fn:any;
constructor(fn) {
this._fn = fn
}
run() {
this._fn()
}
}
export function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
}
我们通过创建一个类 来封装我们 对 收集依赖的所有操作 符合面向对象的方式 就是我们在调用fn的时候 我们去执行 他的run 方法 这时候 我们的测试在 不更新数据的时候是可以通过的
然后我们再去实现 更新操作 这时候我们就需要 收集依赖和 触发 依赖 收集依赖是每一个 reactive 中的每一个 key 发生改变的时候我们都需要去收集 与之 相对应的 一个 fn 依赖 因为我们修改的是对象的属性 所以我们 还需要考虑如何 能够 让一个 一个对象 对应 多个key 每个key 对应不同的fn 依赖 ,完整代码如下
class ReactiveEffect {
private _fn:any;
constructor(fn) {
this._fn = fn
}
run() {
activeEffect = this
this._fn()
}
}
export function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
}
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
}
})
}
let activeEffect
const targetMap = new WeakMap()
export function track(target, key) {
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
deps.add(activeEffect)
}
export function tigger(target, key) {
let depsMap = targetMap.get(target)
let deps = depsMap.get(key)
for(const dep of deps) {
dep.run()
}
}