目标
reactive 方法创建一个响应性对象
- 响应式对象里面有一个容器,用于存放收集的依赖
effect 方法接收一个函数 fn
- effect 用于收集依赖
track 依赖收集
- fn 一开始会调用,即出发响应性对象的 get
- 当响应性对象的 get 触发就会收集 fn 到其容器
trigger 触发依赖
- 当响应性对象 set 触发时,会把容器中所有的 fn 依赖进行调用
effect 的 happy path
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);
});
});
基于 effect,拆分任务先实现 reactive 的 happy path
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 实现
reactive 的本质是一个 proxy 代理,对其 get / set 进行拦截
export function reactive(raw) {
return new Proxy(raw, {
get(target, key) {
const res = Reflect.get(target, key);
// TODO 依赖收集
return res;
},
set(target, key, value) {
const res = Reflect.set(target, key, value);
// TODO 触发依赖
return res;
},
});
}
effect 实现
// 抽离出一个 class 进行封装响应性的操作
class EffectReactive {
private _fn: any;
constructor(fn) {
this._fn = fn;
}
run() {
this._fn();
}
}
export function effect(fn) {
const effectReactive = new EffectReactive(fn);
// effect 提供一个 run 的方法,来调用 fn
effectReactive.run();
}
依赖收集实现
使用当前的被代理的对象 target 从 targetMap 中取出 depsMap, 再由 key 从 depsMap 取出容器 dep。然后就可以添加到 dep 中。
把 fn 保存到容器中
export function reactive(raw) {
return new Proxy(raw, {
get(target, key) {
const res = Reflect.get(target, key);
track(target, key);
return res;
},
});
}
class EffectReactive {
private _fn;
constructor(fn) {
this._fn = fn;
}
run() {
// 保存当前的 effect
activeEffect = this;
this._fn();
}
}
// 通过全局变量,把当前的 effect 保存到 activeEffect
let activeEffect;
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);
}
触发依赖
export function trigger(target, key) {
const depsMap = targetMap.get(target);
const dep = depsMap.get(key);
for (const effect of dep) {
effect.run();
}
}
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;
},
});
}