vue3之computed和watch原理

163 阅读1分钟

本文基于vue3,参考《vue.js设计与实现》

只将核心函数逻辑写出

let activeEffect;

const effectStack = [];

function effect(fn, options) {
    const effectFn = () => {
        cleanup(effectFn);
        activeEffect = effect;
        effectStack.push(effectFn);

        const res = fn();
        effectStack.pop();
        activeEffect = effectStack[effectStack.length - 1];

        return res;
    };

    effectFn.options = options;

    effectFn.deps = [];

    if (!options.lazy) {
        effectFn();
    }
    return effectFn;
}

function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps; i++) {
        const deps = effectFn.deps[i];
        deps.delete(effectFn);

        effectFn.deps.length = 0;
    }
}

function track(target, key) {
    if (!activeEffect) return;
    let depsMap = bucket.get(target);
    if (!depsMap) {
        bucket.set(target, (depsMap = new Map()));
    }
    let deps = depsMap.get(key);
    1;
    if (!deps) {
        deps.set(key, (deps = new Set()));
    }
    deps.add(activeEffect);
    activeEffect.deps.push(deps);
}

function trigger(target, key) {
    const depsMap = bucket.get(target);
    if (!depsMap) {
        return;
    }
    const effects = depsMap.get(key);

    effects &&
        effects.forEach((effectFn) => {
            if (effectFn.options.scheduler) {
                effectFn.options.scheduler(effectFn);
            } else {
                effectFn();
            }
        });

}

function traverse(value, seen = new Set()) {
    // 读取值是原始值或已被读取,什么都不做
    if (typeof value !== 'object' || value === null || seen.has(value)) {
        return
    }
    // 将数据添加到seen中,代表已经读取过了,避免循环引用引起的死循环
    seen.add(value)
    // 只看原理,不考虑过多细节,此处暂不考虑数组等其他数据结构
    // 假设value就是一个对象,递归处理
    for (const k in value) {
        traverse(value[k], seen)
    }
    return value
}

function watch(source, cb, options) {
    let getter

    if (typeof source === 'function') {
        getter = source
    } else {
        getter = () => traverse(source)
    }
    let oldValue
    let newValue
    let cleanup
    function onInvalidare(fn) {
        cleanup = fn
    }
    const job = () => {
        newValue = effectFn()

        if (cleanup) {
            cleanup
        }
        cb(newValue, oldValue, onInvalidare)
        oldValue = newValue
    }
    const effectFn = effect(
        () => getter,
        {
            lazy: true,
            scheduler: () => {
                if (options.flush === 'post') {
                    const p = Promise.resolve()
                    p.then(job)
                } else {
                    job()
                }
            }
        }
    )
    if (options.immediate) {
        job()
    } else {
        oldValue = effectFn()
    }
}

function computed(getter) {
    let value;
    // 缓存
    let dirty = true;

    const effectFn = effect(getter, {
        lazy: true,

        // 调度器,getter中依赖的响应式变化时会执行调度器
        scheduler() {
            // 当计算属性依赖的响应式数据变化时,手动调用trigger触发响应式
            trigger(obj, "value");

            dirty = true;
        },
    });

    const obj = {
        get value() {
            if (dirty) {
                value = effectFn();
                dirty = false;
            }
        },
    };

    return obj;
}