Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

《Vue.js设计与实现》第4章 响应系统的作用与实现 4.5小节 嵌套的effect与effect栈 #329

Open
wangnan0916 opened this issue May 18, 2022 · 2 comments

Comments

@wangnan0916
Copy link

在进行嵌套定义effect时,如果多次修改外部effect中使用的响应式数据,会间接调用内部effect。

let data = {foo: true, bar: false, text: "data.text"};

const bucket = new WeakMap;

const effectStack = [];
effectStack.activeEffect = function () {
    if (this.length) return this[this.length - 1];
    return undefined;
}

let obj = new Proxy(data, {
    set(target, key, newValue) {
        target[key] = newValue;
        trigger(target, key);
        return true;
    },
    get(target, key) {
        track(target, key);
        return target[key];
    }
});

function track(target, key) {
    if (!effectStack.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(effectStack.activeEffect());
    effectStack.activeEffect().deps.push(deps);
}

function trigger(target, key) {
    let depsMap = bucket.get(target);
    if (!depsMap) return;
    let effectsToRun = new Set(depsMap.get(key));
    effectsToRun.forEach(fn => fn());
}

function effect(fn) {
    function effectFn() {
        cleanup(effectFn);
        effectStack.push(effectFn);

        fn();
        effectStack.pop();
    }

    effectFn.sourceFn = fn;

    effectFn.deps = [];
    effectFn();
}

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

function line(text = "") {
    let cnt = 15;
    console.log("--" + text + "-".repeat(cnt));
}

line("init")
effect(function () {
    console.log(`outer effect:obj.foo=${obj.foo}`);
    effect(function () {
        console.log(`inner effect:obj.bar=${obj.bar}`);
    });
});
line("let obj.foo be false");
obj.foo = false;
obj.foo = false;
line("let obj.bar be true");
obj.bar = true;
  1. effect(fn)会将fn包装为effectFn
  2. 修改obj.foo进行重新绑定时,会导致内部的依赖再次绑定
  3. 由于step1,所以bucket的数据结构中的Set无法对effectFn进行去重,也就导致注册多个重复的依赖。

程序的输出结果为:

--init---------------
outer effect:obj.foo=true
inner effect:obj.bar=false
--let obj.foo be false---------------
outer effect:obj.foo=false
inner effect:obj.bar=false
outer effect:obj.foo=false
inner effect:obj.bar=false
--let obj.bar be true---------------
inner effect:obj.bar=true
inner effect:obj.bar=true
inner effect:obj.bar=true

在对内部的响应式数据进行修改时,触发了三个effectFn,其中有初始化的1个,两次对obj.foo进行修改时,间接添加的effectFn
在我个人看来,应该只需要执行一个effectFn,而不是三个,不知道是本意如此,还是意外的BUG。
如果是BUG,我想到了一个不是很好解决的方法:通过将函数转为string来判断是否为相同的函数。例如:

let set = new Set();

function effect(fn) {
    function effectFn() {
        fn();
    }

    set.add(fn.toString());
    effectFn();
}

effect(() => console.log(1));
effect(() => console.log(1));
console.log(set);

涉及修改的代码如下:

function track(target, key) {
    if (!effectStack.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 Map));
    //以函数文本作为key,达到去重效果
    deps.set(effectStack.source, effectStack.activeEffect());
    effectStack.activeEffect().deps.push(deps);
}

function trigger(target, key) {
    let depsMap = bucket.get(target);
    if (!depsMap) return;
    let effectsToRun = new Map(depsMap.get(key));
    effectsToRun.forEach(fn => fn());
}

function effect(fn) {
    function effectFn() {
        cleanup(effectFn);
        effectStack.push(effectFn);

        fn();
        effectStack.pop();
    }

    //存储函数文本
    effectFn.source = fn.toString();

    effectFn.deps = [];
    effectFn();
}

以上是个人看法,如有错误,还请霍老师指正😳。

@TLovers
Copy link

TLovers commented Jul 31, 2022

@wangnan0916 我照着书上敲 activeEffect 和effectStack 的实现 咋和你这个不一样?
不过我倒是发现一个问题,可能我还没有看到后面,就是effectStack 每次pop.最后一次肯定是undefined。那么activeEffect 也会是undefined 那么书上53页track 第一行代码, if(!activeEffect) return 岂不是get不到值了

@wisonxiang
Copy link

嵌套这里确实有bug,外层更新数据时,会触发内层重新执行effect函数,这时内层的cleanup(effectFn),并不会清掉上一次绑定副作用函数

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants