effect的基本实现
export let activeEffect = undefined;
class ReactiveEffect {
active = true;
deps = [];
parent = undefined;
constructor(public fn) { }
run() {
if (!this.active) {
return this.fn();
}
try {
this.parent = activeEffect;
activeEffect = this;
return this.fn();
} finally {
activeEffect = this.parent;
this.parent = undefined;
}
}
}
export function effect(fn, options?) {
const _effect = new ReactiveEffect(fn);
_effect.run();
}
依赖收集
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true;
}
const res = Reflect.get(target, key, receiver);
track(target, 'get', key);
return res;
}
const targetMap = new WeakMap();
export function track(target, type, key) {
if (activeEffect) {
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
let shouldTrack = !dep.has(activeEffect)
if (shouldTrack) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
}
将属性和对应的effect维护成映射关系,后续属性变化可以触发对应的effect函数重新run
触发更新
set(target, key, value, receiver) {
let oldValue = target[key]
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
trigger(target, 'set', key, value, oldValue)
}
return result;
}
export function trigger(target, type, key?, newValue?, oldValue?) {
const depsMap = targetMap.get(target);
if (!depsMap) {
return
}
const effects = depsMap.get(key);
effects && effects.forEach(effect => {
if (effect !== activeEffect) effect.run();
})
}
分支切换与cleanup
在渲染时我们要避免副作用函数产生的遗留
const state = reactive({ flag: true, name: 'jw', age: 30 })
effect(() => {
console.log('render')
document.body.innerHTML = state.flag ? state.name : state.age
});
setTimeout(() => {
state.flag = false;
setTimeout(() => {
console.log('修改name,原则上不更新')
state.name = 'zf'
}, 1000);
}, 1000)
function cleanupEffect(effect) {
const { deps } = effect;
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect);
}
effect.deps.length = 0;
}
class ReactiveEffect {
active = true;
deps = [];
parent = undefined;
constructor(public fn) { }
run() {
try {
this.parent = activeEffect;
activeEffect = this;
+ cleanupEffect(this);
return this.fn();
}
}
}
这里要注意的是:触发时会进行清理操作(清理effect),在重新进行收集(收集effect)。在循环过程中会导致死循环。
let effect = () => {};
let s = new Set([effect])
s.forEach(item=>{s.delete(effect); s.add(effect)});
停止effect
export class ReactiveEffect {
stop(){
if(this.active){
cleanupEffect(this);
this.active = false
}
}
}
export function effect(fn, options?) {
const _effect = new ReactiveEffect(fn);
_effect.run();
const runner = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}
调度执行
trigger 触发时,我们可以自己决定副作用函数执行的时机、次数、及执行方式
export function effect(fn, options:any = {}) {
const _effect = new ReactiveEffect(fn,options.scheduler);
_effect.run();
const runner = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}
export function trigger(target, type, key?, newValue?, oldValue?) {
const depsMap = targetMap.get(target);
if (!depsMap) {
return
}
let effects = depsMap.get(key);
if (effects) {
effects = new Set(effects);
for (const effect of effects) {
if (effect !== activeEffect) {
if(effect.scheduler){
effect.scheduler()
}else{
effect.run();
}
}
}
}
}
深度代理
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true;
}
const res = Reflect.get(target, key, receiver);
track(target, 'get', key);
if(isObject(res)){
return reactive(res);
}
return res;
}
当取值时返回的值是对象,则返回这个对象的代理对象,从而实现深度代理
总结
- 为了实现响应式,我们使用了new Proxy
- effect默认数据变化要能更新,我们先将正在执行的effect作为全局变量,渲染(取值),然后在get方法中进行依赖收集
- 依赖收集的数据格式weakMap(对象:map(属性:set(effect))
- 用户数据发生变化,会通过对象属性来查找对应的effect集合,全部执行;
- 调度器的实现,创建effect时,把scheduler存在实例上,调用runner时,判断如果有调度器就调用调度器,否则执行runner
|