前言
计算属性,功能和vue2.x基本等同,本文主要对其功能进行模拟实现
一.完整代码
const reactMap = new WeakMap();
const ReactiveFlags = {
IS_REACTIVE : "isReactive"
}
let activeEffect = undefined;
class ReactiveEffect {
active = true;
deps = [];
constructor(fn,scheduler) {
this.fn = fn;
this.scheduler = scheduler;
}
run() {
if(!this.active) {this.fn()};
try {
activeEffect = this;
return this.fn()
} finally {
activeEffect = undefined;
}
}
}
class ComputedRefImpl {
effect;
_value;
dep = new Set();
_dirty = true;
_v_isReadOnly = true;
_v_isRef = true;
constructor(getter,setter) {
this.effect = new ReactiveEffect(getter, () => {
if(!this._dirty) {
this._dirty = true;
triggerEffect(this.dep)
}
})
}
get value() {
trackEffect(this.dep)
if(this._dirty) {
this._dirty = false;
this._value = this.effect.run()
}
return this._value
}
}
function effect(fn) {
const _effect = new ReactiveEffect(fn);
_effect.run();
const runner = _effect.run.bind(_effect);
runner.effect = runner;
return runner
}
function reactive(target) {
if(!(typeof target === 'object' && target !== null)) {
return;
}
if(target[ReactiveFlags.IS_REACTIVE]) {
return target
}
let exisProxy = reactMap.get(target);
if(exisProxy) {
return exisProxy
}
const proxy = new Proxy(target,{
get(target,key,receiver) {
if(key === ReactiveFlags.IS_REACTIVE) {
return true
}
track(target,'get',key)
return Reflect.get(target,key,receiver)
},
set(target,key,value,receiver) {
let oldValue = target[key];
let result = Reflect.set(target,key,value,receiver);
if(oldValue !== value) {
trigger(target,'set',key,oldValue,value)
}
return result
}
});
reactMap.set(target,proxy)
return proxy
}
function computed(getterOrOptions) {
let onlyGetter = typeof getterOrOptions === 'function'
let getter;
let setter;
if(onlyGetter) {
getter = getterOrOptions;
setter = () => {console.warn('no set')}
} else {
const {get,set} =getterOrOptions;
[getter,setter] = [get,set]
}
return new ComputedRefImpl(getter,setter)
}
const targetMap = new WeakMap()
function trigger(target,type,key,oldValue,value) {
const depsMap = targetMap.get(target);
if(!depsMap) return;
const effects = depsMap.get(key);
triggerEffect(effects,'effects')
}
function triggerEffect(effects) {
effects && effects.forEach(effect => {
if(effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
})
}
function track(target,type,key) {
if(!activeEffect) return ;
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()))
}
trackEffect(dep)
}
function trackEffect(dep) {
if(activeEffect) {
let shouldTrack =!dep.has(activeEffect);
if(shouldTrack) {
dep.add(activeEffect)
activeEffect.deps.push(dep);
}
}
}
const target ={name: 'sandy',age: 18,height: 195};
const r1 = reactive(target);
const formatData = computed (() => {
const {name,age} = r1;
return `format123 ${name},${age}`
})
effect(() => {
const { value } = formatData;
console.log(`effect ${value}`)
})
setTimeout(() => {
r1.name = 'wendy'
},2000)
二. 调度器
为的是方便于当监听值改变后,自定义逻辑代码
constructor(getter,setter) {
this.effect = new ReactiveEffect(getter, () => {
if(!this._dirty) {
this._dirty = true;
triggerEffect(this.dep)
}
})
}
function triggerEffect(effects) {
effects && effects.forEach(effect => {
if(effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
})
}
三. 计算属性逻辑梳理
- 相关调用代码
const target ={name: 'sandy',age: 18,height: 195};
const r1 = reactive(target);
const formatData = computed (() => {
const {name,age} = r1;
return `format123 ${name},${age}`
})
effect(() => {
const { value } = formatData;
console.log(`effect ${value}`)
})
setTimeout(() => {
r1.name = 'wendy'
},2000)
- 逻辑梳理及触发顺序
(1)computed里面的函数执行,并完成初始化操作,此时fn和调度器均已完成初始化操作; (2)effect里面的函数立即执行一次,因为函数里面访问了计算属性的value属性,所以会先访问ComputedRefImpl类中的get; (3) trackEffect(this.dep) 和 this._value = this.effect.run()完成了响应数据属性的依赖收集; (4) 当2s之后改变属性值之后,会先触发proxy中的set,调用完成的trigger函数,然后再触发ComputedRefImpl类中的调度器函数; (5)调度器函数执行完之后,则computed计算属性更新完毕,则继续执行effcet中函数的代码逻辑; 总结:因为effcet函数作用域使用了计算属性的值,所以核心代码逻辑是 :响应数据更改 ——》计算属性值更改 ——》effect执行完毕
四. 计算属性的缓存特性实现
声明了_dirty属性,_dirty默认为true,当_dirty为true时,则表明需重新计算,为false时,不需要重新计算。 当属性值改变时,在调度器中将_dirty设置为true,表示之后访问时需要重新计算; 当访问了计算属性的value之后,表示已经访问过了,不需要重新计算,将_dirty设置为false
|