目标
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 的方法,来调用 fneffectReactive.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() {// 保存当前的 effectactiveEffect = this;this._fn();}}// 通过全局变量,把当前的 effect 保存到 activeEffectlet 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;},});}
