第三节:vue3-Computed实现原理

881 阅读2分钟

接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象

computed的特点是缓存,计算属性是一个effect,计算属性收集外层effect

多次取值 如果依赖的属性未发生变化是不会重新计算的

__dirty 计算属性中的缓存标识,如果依赖有变化重新执行(this.effect的 调度函数执行)没变化不重新执行

  • 1、在constructor 中将用户的getter放到effec中, 能对getter函数 使用到的属性进行依赖收集 activeEffect会变为 getter
  • 2、创建ReactiveEffect时,传入scheduler函数,稍后依赖的属性变化时调用此方法
  • 3、取值的时候进行依赖收集
  • 4、_dirty=true是脏值, 执行函数,其实执行的是 scheduler 调度函数在其中 触发更新triggerEffects

使用

const {effect, reactive} = VueReactivity
const state = reactive({firstname: 'l',lastname: 'yp'})
const fullname = computed({
    get(){
        return state.firstname + state.lastname
    },
    set(newValue){
        console.log(newValue)
    }
})
const fullname2 = computed(() => {
    return state.firstname + state.lastname
})
// console.log(fullname.value)
// fullname.value = 'jdlyp'
effect(()=>{
    app.innerHtml = fullname.value
})
setTimeout(()=>{
    state.firstname = 'jdl'
}, 1000)

入口函数

export const isFunction = (value) => {
    return  typeof value === 'function'
}
export const computed = (getterOrOptions) => {
    let onlyGetter = isFunction(getterOrOptions)
    let getter;
    let setter;
    if (onlyGetter) {
        getter = getterOrOptions;
        setter = () => { console.log('on set') }
    } else {
        getter = getterOrOptions.get;
        setter = getterOrOptions.set;
    }
    // 创建计算属性
    return new ComputedRefImpl(getter, setter)
}

实现

// 见第二节:vue3-Reactivity模块-effect
import { 
    activeEffect, 
    ReactiveEffect, 
    trackEffects, 
    triggerEffects 
} from './effect'
class ComputedRefImpl {
    public effect;
    public _value;
    public dep= new Set;
    public _dirty = true; // 数据是否更新的标识也叫缓存标识也叫脏数据标识 默认应该取值的时候进行计算
    public __v_isReadonly = true
    public __v_isRef = true

    constructor(public getter, public setter) {
        // 将用户的getter放到effec中, 能对getter函数进行依赖收集 activeEffect会变为 
        // 用getter 生成的 effect
        // 这里边 firstname 和lastname就会被getter收集起来
        // 创建ReactiveEffect时,传入`scheduler`调度函数,稍后依赖的属性变化时调用此方法
        this.effect = new ReactiveEffect(getter, () => {
            // 稍后依赖属性变化会执行此调度函数
            if(!this._dirty){ // 依赖的值变化更新dirty并触发更新
                this._dirty = true;
                // 实现一个触发更新
                triggerEffects(this.dep)
            }
        })
    }
    // 类中的属性访问器 底层就是Objecy.defineProperty 
    get value() { 
        // 取值的时候进行依赖收集
        if(activeEffect){
            trackEffects(this.dep);
        }

        if(this._dirty){ // 第一次是true 说明是脏值, 执行函数
            this._dirty = false;
            // 其实执行的是 scheduler 调度函数在其中 触发更新triggerEffects
            this._value = this.effect.run();  
        }
        return this._value
    }
    set value(newValue) {
        this.setter(newValue)
    }
}

依赖收集和触发更新

是从上一章 vue3-Reactivity模块-effect 中 的 trigger、 track 函数中提取的

// get的时候 依赖收集
export function trackEffects(dep) { // 收集dep 对应的effect
    let shouldTrack = !dep.has(activeEffect)
    if (shouldTrack) {
        dep.add(activeEffect);
        activeEffect.deps.push(dep); 
    }
}

// 更新effect
export function triggerEffects(effects) { 
    effects = new Set(effects);
    for (const effect of effects) {
        if (effect !== activeEffect) { // 如果effect不是当前正在运行的effect
            if (effect.scheduler) {
                effect.scheduler()
            } else {
                effect.run(); // 重新执行一遍
            }
        }
    }
}