0
点赞
收藏
分享

微信扫一扫

vue3的响应式原理(三)

三、activeEffect 和 ref

在上一节代码中,还存在一个问题: track 函数中的依赖( effect 函数)是外部定义的,当依赖发生变化, track 函数收集依赖时都要手动修改其依赖的方法名。

比如现在的依赖为 foo 函数,就要修改 track 函数的逻辑,可能是这样:

const foo = () => { /**/ };
const track = (target, key) => {     // ②
  // ...
  dep.add(foo);
}

那么如何解决这个问题呢?

1. 引入 activeEffect 变量

接下来引入 activeEffect 变量,来保存当前运行的 effect 函数。

let activeEffect = null;
const effect = eff => {
  activeEffect = eff; // 1. 将 eff 函数赋值给 activeEffect
  activeEffect();     // 2. 执行 activeEffect
  activeEffect = null;// 3. 重置 activeEffect
}

然后在 track 函数中将 activeEffect 变量作为依赖:

const track = (target, key) => {
    if (activeEffect) {  // 1. 判断当前是否有 activeEffect
        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()));
        }
        dep.add(activeEffect);  // 2. 添加 activeEffect 依赖
    }
}

使用方式修改为:

effect(() => {
    total = product.price * product.quantity
});

这样就可以解决手动修改依赖的问题,这也是 Vue3 解决该问题的方法。完善一下测试代码后,如下:

const targetMap = new WeakMap();
let activeEffect = null; // 引入 activeEffect 变量

const effect = eff => {
  activeEffect = eff; // 1. 将副作用赋值给 activeEffect
  activeEffect();     // 2. 执行 activeEffect
  activeEffect = null;// 3. 重置 activeEffect
}

const track = (target, key) => {
    if (activeEffect) {  // 1. 判断当前是否有 activeEffect
        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()));
        }
        dep.add(activeEffect);  // 2. 添加 activeEffect 依赖
    }
}

const trigger = (target, key) => {
    const depsMap = targetMap.get(target);
    if (!depsMap) return;
    let dep = depsMap.get(key);
    if (dep) {
        dep.forEach(effect => effect());
    }
};

const reactive = (target) => {
    const handler = {
        get(target, key, receiver) {
            const result = Reflect.get(target, key, receiver);
            track(target, key);
            return result;
        },
        set(target, key, value, receiver) {
            const oldValue = target[key];
            const result = Reflect.set(target, key, value, receiver);
            if (oldValue != result) {
                trigger(target, key);
            }
            return result;
        }
    }

    return new Proxy(target, handler);
}

let product = reactive({ price: 10, quantity: 2 });
let total = 0, salePrice = 0;
// 修改 effect 使用方式,将副作用作为参数传给 effect 方法
effect(() => {
    total = product.price * product.quantity
});
effect(() => {
    salePrice = product.price * 0.9
});
console.log(total, salePrice);  // 20 9
product.quantity = 5;
console.log(total, salePrice);  // 50 9
product.price = 20;
console.log(total, salePrice);  // 100 18

思考一下,如果把第一个 effect 函数中 product.price 换成 salePrice 会如何:

effect(() => {
    total = salePrice * product.quantity
});
effect(() => {
    salePrice = product.price * 0.9
});
console.log(total, salePrice);  // 0 9
product.quantity = 5;
console.log(total, salePrice);  // 45 9
product.price = 20;
console.log(total, salePrice);  // 45 18

得到的结果完全不同,因为 salePrice 并不是响应式变化,而是需要调用第二个 effect 函数才会变化,也就是 product.price 变量值发生变化。

代码地址: https://github.com/Code-Pop/vue-3-reactivity/blob/master/05-activeEffect.js

2. 引入 ref 方法

熟悉 Vue3 Composition API 的朋友可能会想到 Ref,它接收一个值,并返回一个响应式可变的 Ref 对象,其值可以通过 value 属性获取。

ref:接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property .value。

官网的使用示例如下:

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

我们有 2 种方法实现 ref 函数:

  1. 使用 reactive 函数

const ref = intialValue => reactive({value: intialValue});

这样是可以的,虽然 Vue3 不是这么实现。

  1. 使用对象的属性访问器(计算属性)

属性方式包括:getter 和 setter。

const ref = raw => {
  const r = {
    get value(){
      track(r, 'value');
      return raw;
    },
    
    set value(newVal){
    	raw = newVal;
      trigger(r, 'value');
    }
  }
  return r;
}

使用方式如下:

let product = reactive({ price: 10, quantity: 2 });
let total = 0, salePrice = ref(0);
effect(() => {
    salePrice.value = product.price * 0.9
});
effect(() => {
    total = salePrice.value * product.quantity
});
console.log(total, salePrice.value); // 18 9
product.quantity = 5;
console.log(total, salePrice.value); // 45 9
product.price = 20;
console.log(total, salePrice.value); // 90 18

在 Vue3 中 ref 实现的核心也是如此。

代码地址: https://github.com/Code-Pop/vue-3-reactivity/blob/master/06-ref.js

举报

相关推荐

Vue3 响应式原理

Vue3响应式原理实现

vue3响应式原理-effect

vue3中的响应式原理

vue3的响应式实现原理

Vue3响应式原理解析

0 条评论