vue设计与实现 第4章 computed 响应原理 笔记

computed 可借助于effect 实现,只需要扩展其懒加载和缓存功能

 

effect 代码

复制代码
let activeEffect;
const effectStack = [];
function effect(fn, options = {}) {
    const effectFn = () => {
        cleanup(effectFn)
        activeEffect = effectFn
        effectStack.push(effectFn)
        fn()
        effectStack.pop()
        activeEffect = effectStack[effectStack.length - 1]
    }
    effectFn.deps = []
    effectFn.options = options
    effectFn()

}
const cleanup = (effectFn) => {
    for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i]
        deps.delete(effectFn)
    }
    effectFn.deps.length = 0
}
const data = { status: true, text: 'asd',num: 0 };
const bucket = new WeakMap();
const obj = new Proxy(data, {
    get(target, key) {
        track(target, key)
        return target[key]
    },
    set(target, key, newVal) {
        target[key] = newVal
        trigger(target, key)
    }
})
const track = (target, key) => {
    if (!activeEffect) return
    let depsMap = bucket.get(target)
    if (!depsMap) bucket.set(target, (depsMap = new Map()));
    let deps = depsMap.get(key)
    if (!deps) depsMap.set(key, (deps = new Set()));
    deps.add(activeEffect)
    activeEffect.deps.push(deps)
}
const trigger = (target, key) => {
    let depsMap = bucket.get(target)
    if (!depsMap) return
    let effects = depsMap.get(key)
    const effectsToRun = new Set()
    
    effects.forEach(fn => {
        if (fn !== activeEffect) {
            effectsToRun.add(fn)
        }
    })
    
    effectsToRun.forEach(fn => {
        fn.options.scheduler ? fn.options.scheduler(fn) : fn()
    })
}
View Code
复制代码

 之前调用 effect 函数就会立即执行一次,传递过去的副作用函数,而且没返回结果。实现 computed 懒加载功能和返回结果很简单。只需要在 options 添加个状态来管理,和把副作用函数结果直接 return。

扩展代码  懒加载

复制代码
function effect(fn, options = {}) {
    const effectFn = () => {
        cleanup(effectFn)
        activeEffect = effectFn
        effectStack.push(effectFn)
        const res = fn(); // 执行传递过来的副作用函数,保存结果
        effectStack.pop()
        activeEffect = effectStack[effectStack.length - 1]
        return res // 返回结果
    }
    effectFn.deps = []
    effectFn.options = options
   
    // 只有 lazy 为 false 执行,返回包装过的副作用函数
    if(!options.lazy) { effectFn() }
    return effectFn
}
effect(()=>{
    console.log(obj.num);
},{
    lazy: true
})
复制代码

扩展完我们可以开始 封装 computed 函数 

复制代码
function computed(getter) {
    // 把传递给 computed 的 getter 函数 传递给 effect 作为副作用函数
    const  effectFn = effect(getter,{
        lazy: true
    })
    // computed 返回一个 对象,获取 value 属性的时候 调用 effect 副作用函数,并返回其返回值
    const obj = {
        get value(){
            return effectFn()
        }
    }
    return obj
}
const increment = computed(()=>obj.num+1)
increment.value // 1
复制代码

大概意思就是在 computed 函数里边调用 effect ,创建一个对象,并把副作用函数当作这个对象的 value 属性的 get 函数,返回这个对象。所以只要调用 computed 返回值的 value 属性便能够手动

触发副作用函数。但是,现在还没有缓存计算结果,每次调用 computed 都是重新执行一遍副作用函数。

 

继续扩展  缓存

复制代码
function computed(getter) {
    let value; // 缓存数据
    let flush = true // 添加状态控制缓存数据
    const  effectFn = effect(getter,{
        lazy: true,
        // 响应式数据发生变化的时候, 发现有 scheduler 会调用 scheduler
        scheduler(){
            flush = true // 响应式数据改变,开放状态,
        }
    })
    const obj = {
        get value(){
            if(flush){ // true 表示数据改变需要重新获取数据
                value = effectFn()
                flush = false // 获取数据完毕,关闭状态
            }
            return value
        }
    }
    return obj
}
const increment = computed(()=>obj.num+1)
console.log(increment.value );// 1
console.log(increment.value );// 1
obj.num++
console.log(increment.value );// 2
复制代码

实现也是非常简单,添加 flush 变量控制。初始值和响应式数据变化 赋值为 true,调用副作用函数后赋值为 false。

 

现在的 computed 还有一个缺陷,在 effect 中读取计算属性值时, 数据改变没触发副作用函数。例子

const increment = computed(()=>obj.num+1)
effect(()=>{
    console.log(increment.value);
})
obj.num++   // effect 没触发

 

解决方法:获取计算属性的时候手动触发 track 收集 obj的value属性的副作用函数, computed 依赖的数据改变的时候会触发 scheduler 函数,这时候再手动触发用到 obj的value 属性的副作用函数,也就是 使用了计算属性的副作用函数

复制代码
function computed(getter) {
    let value; 
    let flush = true 
    const  effectFn = effect(getter,{
        lazy: true,

        scheduler(){
            flush = true
            trigger(obj,'value') // 触发 obj value 属性的 effect 函数
        }
    })
    const obj = {
        get value(){
            if(flush){ 
                value = effectFn()
                flush = false
            }
            track(obj,'value') // 收集 使用了 obj value 属性的 effect 函数
            return value
        }
    }
    return obj
}
复制代码

 

posted @   。吃什么  阅读(228)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示