阅读 200

Vue3 响应式原理探索Part 5 - computed values 的实现

“这是我参与更文挑战的第10天,活动详情查看: 更文挑战

前文摘要

通过之前的学习,我们已经通过 Proxy + Reflect 搭建出了一个基础的响应式实现,并探索了 ref 的原理和实现。

本文我们将学习 Computed Values 的实现。

computed 用法

回到之前 ref 的示例代码如下:

let param = reactive({ width: 5, height: 2  });
let size = 0;
let newWidth = ref(0);

effect(() => {
    newWidth.value = param.width * 2;
});

effect(() => {
    size = newWidth.value * param.height;
});
复制代码

如果你已经熟悉了 Vue3 的用法,你会更习惯以下简洁的用法。

 let param = reactive({ width: 5, height: 2  });
        
let newWidth = computed(() => {
    return param.width * 2
});

let size = computed(() => {
    return newWidth.value * param.height
});

复制代码

它相对来说简洁优雅很多,只用 computed 包裹并赋值一次,用到的时候只要取 .value 即可。

computed 实现

function computed(getter) {
  let result = ref()  // 创建一个新的响应引用

  effect(() => (result.value = getter())) // 在 effect 调用时, 将 getter 的返回值赋值给 result.value ,

  return result // 返回引用
}
复制代码

非常简洁。整体代码如下:

const targetMap = new WeakMap();
let activeEffect = null // The active effect running

function track(target, key) {
    if (!activeEffect) return;
    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);
}
function trigger(target, key) {
    const depsMap = targetMap.get(target);
    if (!depsMap) return;
    let dep = depsMap.get(key);
    if (dep) {
        dep.forEach(element => {
            element();
        });
    }
}

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

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

function effect(eff) {
    activeEffect = eff  // Set this as the activeEffect
    activeEffect()      // Run it
    activeEffect = null // Unset it
}

function computed(getter) {
    let result = ref()  // Create a new reactive reference

    effect(() => (result.value = getter())) // Set this value equal to the return value of the getter

    return result // return the reactive reference
}

let param = reactive({ width: 5, height: 2  });

let newWidth = computed(() => {
    return param.width * 2
});

let size = computed(() => {
    return newWidth.value * param.height
});

复制代码

再执行如下代码,将会 console 出预期的结果。


console.log(`newWidth is ${newWidth.value}, size is ${size}`); // newWidth is 10, size is 20

param.width = 6;

console.log(`newWidth is ${newWidth.value}, size is ${size}`); // newWidth is 12, size is 24
复制代码

Vue3 源码小析

我们已经建立了一个响应式的系统了(嗯,当然是非常弱鸡版本的……)。在 Vue 的真实版本中,是比这个复杂N倍的。我们可以简单列举一下Vue 源码中响应式实现相关的文件。

主要目录在 github.com/vuejs/vue-n…

主要结构如下(截止2021年6月10日):

vue.png

有兴趣的同学可以直接阅读源码。

小结

  • computed 用法更简洁优雅
  • computed 的封装实现基于 refreactive 函数
文章分类
前端
文章标签