模拟实现vue3.x中的计算属性

前言

计算属性,功能和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();
    // computed新增
    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)

// setTimeout(() => {
//     console.log(formatData.value,'formatData.value')
// },3000)

二. 调度器

为的是方便于当监听值改变后,自定义逻辑代码

//声明
 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()
        }
    })
}

三. 计算属性逻辑梳理

  1. 相关调用代码
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. 逻辑梳理及触发顺序
    (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

你可能感兴趣的:(vue源码,javascript,前端,typescript)