主要代码
const targetMap = new Map();
let activeEffect!: ReactiveEffect;
let shouldTrack = false;
export const isTracking = () => {
return shouldTrack && activeEffect.active == true;
}
const track = (target, key) => {
if (!isTracking()) return;
let depsMap: Map<any, any> = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let deps: Set<any> = depsMap.get(key);
if (!deps) {
deps = new Set();
depsMap.set(key, deps);
}
if (deps.has(key)) return;
trackEffect(deps);
}
export const trackEffect = (deps) => {
deps.add(activeEffect);
activeEffect.deps.add(deps);
}
const trigger = (target, key) => {
let tar: Map<any, any> = targetMap.get(target);
let deps: Set<any> = tar.get(key);
triggerEffect(deps)
}
export const triggerEffect = (deps) => {
for(const effect of deps) {
if (effect.scheduler) {
effect.scheduler();
} else {
effect.run();
}
}
}
export const 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;
}
})
}
class ReactiveEffect {
constructor(private fn: Function, _scheduler?: Function) {
_scheduler && (this.scheduler = _scheduler);
}
public scheduler!: Function;
public onStop!: Function;
public active = true;
public deps: Set<Set<any>> = new Set();
run() {
if (!this.active) {
return this.fn();
}
shouldTrack = true;
activeEffect = this;
const res = this.fn();
activeEffect = null as any;
shouldTrack = false;
return res;
}
stop() {
if (this.active) {
if (this.onStop) {
this.onStop();
}
for(const dep of this.deps) {
dep.clear();
}
this.deps.clear();
this.active = false;
}
}
}
export const effect = (fn: Function, options: {
scheduler?: Function,
onStop?: Function
} = {}) => {
const effect = new ReactiveEffect(fn);
Object.assign(effect, options)
effect.run();
const runner: any = effect.run.bind(effect);
runner.effect = effect;
return runner;
}
export const stop = (runner: Function& {effect: ReactiveEffect}) => {
runner.effect.stop();
}
对应测试用例
import { effect, stop, reactive } from '../index'
describe('effect', () => {
it('happy path', () => {
const user = reactive({
age: 10
})
let nextAge = 0;
effect(() => {
nextAge = user.age + 1
});
expect(nextAge).toBe(11);
user.age++;
expect(nextAge).toBe(12);
})
it('should return runner when call effect', () => {
let foo = 10;
const runner = effect(() => {
foo++;
return 'foo'
})
expect(foo).toBe(11);
const r = runner();
expect(foo).toBe(12);
expect(r).toBe('foo');
})
it('scheduler', () => {
let run: any;
const scheduler = jest.fn(() => {
run = runner;
})
const obj = reactive({ foo: 1 });
let dummy = 0;
const runner = effect(() => {
dummy = obj.foo;
}, { scheduler })
expect(scheduler).not.toHaveBeenCalled();
expect(dummy).toBe(1);
obj.foo++;
expect(scheduler).toHaveBeenCalledTimes(1);
expect(dummy).toBe(1);
run();
expect(dummy).toBe(2);
})
it('stop', () => {
let dumy;
const obj = reactive({ prop: 1 })
const runner = effect(() => {
dumy = obj.prop;
})
obj.prop = 2;
expect(dumy).toBe(2);
stop(runner);
obj.prop = 3;
expect(dumy).toBe(2);
runner();
expect(dumy).toBe(3)
})
it('onStop', () => {
let dumy;
const obj = reactive({ prop: 1 })
const onStop = jest.fn();
const runner = effect(() => {
dumy = obj.prop;
}, { onStop })
expect(dumy).toBe(1)
expect(onStop).not.toHaveBeenCalled();
stop(runner);
expect(onStop).toHaveBeenCalledTimes(1);
})
})
流程
- 通过
reactive创建可收集依赖的数据集,在effect(vue中改名叫watchEffect)函数中调用数据集,
- 调用effec函数,并传入一个function,functin为自定义的内容
- effect函数内部会创建一个
ReactiveEffect函数,并设置为全局变量,同时将function传递到ReactiveEffect中,并调用function命名为run
- 此时自定义的function中调用
rreactive创建的数据集,会触发get/set操作,get操作时候,会检查当前是否有全局变量ReactiveEffect,有则创建一个deps将全部变量塞入到deps中
5.当给数据集赋值时候,会触发set操作,set会去自身的deps里寻找是否有ReactiveEffect,
当找到时候调用ReactiveEffect中的run,来重新触发自定义函数的运行