Vue3响应式及ref、reactive

392 阅读2分钟

Vue的双向数据绑定本质上就是数据劫持和发布订阅。无论是Vue2.x的Object.defineProperty()还是Vue3的proxy,实现响应式原理都是一致的,只是方法发生了改变。


1. 无响应式

let a = 10;
let b = a + 10;
console.log('b :>> ', b); // 20
a = 20;
console.log('b :>> ', b); // 20
b = a + 10;
console.log('b :>> ', b); // 30

2. 基础响应式(本质就是发布订阅,依赖收集)

let curEffect = null;

class Dep {
    constructor(val) {
        // 收集的依赖不会重复,所以使用set
        this.effects = new Set();
        this._val = val;
    }

    get value() {
        this.depend();
        return this._val;
    }

    set value(newVal) {
        if (Object.is(this._val, newVal)) return;
        this._val = newVal;
        this.notify();
    }

    depend() {
        // 因为curEffect可能不存在,一定判断否则notify报错。
        if (curEffect) {
            // 做一个优化:如果依赖存在就不收集
            if (!this.effects.has(curEffect)) {
                this.effects.add(curEffect);
            }
        }
    }

    notify() {
        this.effects.forEach(fn => fn());
    }
}

function effectWatch(effct) {
    curEffect = effct;
    effct();
    curEffect = null;
}

let dep = new Dep(10);

let b;
effectWatch(() => {
    b = dep.value + 10;
    console.log('b :>> ', b); // 20 30
})

dep.value = 20;

3. reactive proxy

// 用于存储对象 key值为对象 使用WeakMap便于垃圾回收
let targetMap = new WeakMap();
// targetMap -> {
    Object: {
        Object[key]: Dep,
        ...
    },
    ...
}

function getDep(target, key) {
    // 获取map中的值
    let depsMap = targetMap.get(target);
    // 如果值不存在,则添加
    if (!depsMap) {
        depsMap = new Map();
        targetMap.set(target, depsMap);
    }
    // 给对象的每个key值为Dep
    let dep = depsMap.get(key);
    // 如果值不存在,则添加
    if (!dep) {
        dep = new Dep();
        depsMap.set(key, dep);
    }
    return dep;
}

function reactive(raw) {
    // 如果不是对象就直接返回
    if (typeof raw === 'object' && raw !== null) {
        return new Proxy(raw, {
            get(target, key) {
                const dep = getDep(target, key);
                dep.depend();
                const value = Reflect.get(target, key);
                // 如果值的类型还是对象,递归。否则无法全部代理到
                // 源码中还有shallowX方法,只响应式第一层
                if(value && typeof value === 'object') return reactive(value);
                return value;
            },
            set(target, key, value) {
                const oldVal = Reflect.get(target, key);
                // 如果值相同,则直接返回
                if (Object.is(value, oldVal)) return;
                Reflect.set(target, key, value);
                const dep = getDep(target, key);
                dep.notify();
            }
        });
    }
    else {
        return raw;
    }
}

const user = reactive({
    name: '朝九',
    age: 18,
    count: {
        num: 1
    }
});

let nextYearAge;
let newCount;

effectWatch(() => {
    nextYearAge = user.age + 1;
    newCount = user.count.num + 1;
    console.log('newCount :>> ', newCount); // 19 21
    console.log('nextYearAge :>> ', nextYearAge); // 2 21
})

user.age = 20;
user.count.num = 20;

4. ref

// ref定义的对象也需要通过.value找到,但我这里只做简单处理,以实现功能。
function ref(value) {
    if (value !== null && typeof value === 'object') {
        return reactive(value);
    }
    // vue3会把所有的响应式最终作为一个对象使用
    return new Dep(value);
}

let count = ref(1);
let doubleCount;

effectWatch(() => {
    doubleCount = count.value * 2;
    console.log('doubleCount :>> ', doubleCount); // 2 4
})

count.value = 2;

let list = ref([{num: 1}, {num: 2}]);
let sum;

effectWatch(() => {
    sum = 0;
    list.forEach(item => {
        sum += item.num;
    })
    console.log('sum :>> ', sum); // 3 4
})

list[0].num = 2;