一路升级打怪---reactivity响应式实现原理

555 阅读4分钟

V1原始状态

声明a和b两个变量,当a发生变化了,b的值也会变化,这就是响应式的雏形。

let a=10;
let b;
function update(){
    b=a+10;
    console.log(b);
}
a=20;
update();//需要主动调用函数才会发生变化

V2使用Vue3里面的响应式对象reactive和effect来实现响应式

如果a变化了,我想让b自动更新

在effect函数中收集依赖,收集的依赖是响应式对象发生的变化,就是effect接收的回调函数,

import { effect, reactive } from require("@vue/reactivity");
//声明一个响应式对象
let a = reactive({
    value: 1
});
let b;

//effect函数一上来先执行一次,然后当响应式的值一旦发生变化,会再执行一次
effect(() => {
    //函数:更新b的值
    b = a.value + 10;
    console.log(b);
})
//effect函数再执行两次
a.value = 30;//这时当a发生改变时,不再需要调用update()了
a.value = 50;

//在vue3里面除了使用reactivity去声明一个响应式对象,还可以使用ref,
//一般是在ref里使用一个单值,ref->number 或 string类型

V3手写实现响应式

响应式的核心:收集依赖&&触发依赖

//响应式库
//依赖
//声明一个全局变量,用于Dep类与effectWatch函数之间建立关系
let currentEffect;//闭包的应用!!!!!!!!数据共享 建立关联
//声明一个类,使用订阅发布模式
class Dep {
    constructor(val) {
        //存储不重复依赖数据集合
        //收集的依赖使用set集合存储
        this.effects = new Set();//存储收集到的依赖
        this._val = val;
    }
    //1.收集依赖
    depend() {
        //如果currentEffect有值,则收集依赖到set集合中
        if (currentEffect) {
            this.effects.add(currentEffect);
        }
    }
    //获取value值
    get value() {
        this.depend();//收集依赖
        return this._val;
    }
    //当传入的val值发生变化时
    set value(newVal) {
        this._val = newVal;
        this.notice();//set函数触发依赖,这里需注意:一定要在值更新以后再触发依赖
    }
    //2.触发依赖:执行之前订阅的一些响应式事件(数据劫持)
    notice() {
        //触发一下我们之前收集到的依赖
        this.effects.forEach((effect) => {
            effect();
        })
    }
}
export function effectWatch(effect) {
    //收集依赖
    currentEffect = effect;
    //回调函数一上来先调用一次
    effect()
    // dep.depend();//这一步可以不调用,可以将这一步写到get函数中,收集依赖
    currentEffect = null;
}

测试代码

let b;
const dep = new Dep(10);
effectWatch(() => {
    b = dep.value + 10;
    console.log(b);
})
//值发生变更
dep.value = 30;

V4手写reactive使用Vue3中的Proxy

刚刚上面实现的响应式是针对单个值的变化,dep->number string

reactive响应式是针对一个对象 对象有对应的key->dep 一个key对应一个dep

这个对象是在什么时候改变的? object.a->get object.a=2 ->set进行拦截更新

vue2使用了defineProperty方法 它有一个属性描述符,用属性描述符的变化去随意的修改属性的值,可以设置set get方法;

缺点:1.有很多属性覆盖不到,2.属性得一个一个设置,如果需要嵌套执行时是非常消耗性能的,执行周期是在初始化(new)的时候就需要执行的,性能消耗还是非常明显的。

vue3使用了proxy(代理对象) 拦截 如果对象中有100个属性,不用执行100次,执行一次就ok了。

//声明一个全局的map,用于存储dep
const targetMap = new Map();
//这个函数将key与dep做一个映射
function getDep(target,key) {
    let depsMap = targetMap.get(target);
    if (!depsMap) {
        depsMap = new Map();
        targetMap.set(target, depsMap);
    }
    let dep = depsMap.get(key);
    if (!dep) {
        //如果dep不存在,new一个实例,并添加到depsMap中,key与dep做一个映射
        dep = new Dep();
        depsMap.set(key, dep);
    }
    return dep;
}
//obj是一个对象
export function reactive(obj) {
    //new proxy里面的参数表示:第一个拦截的对象,第二个是如何处理
    return new Proxy(obj, {
        //Proxy的好处:无论传入的obj对象中有多少个属性,只需要设置一次proxy,所有属性的get都会被代理
        //当obj对象被访问的时候,get就会被触发
        get(target, key) {
            // console.log(key);//age
            // console.log(target);//{age:18}
            //key->dep 让key和dep做一个匹配
            //dep 我们存储在哪里
            const dep = getDep(target,key);
            //让dep对象收集依赖
            dep.depend();
            //return target[key]
            return Reflect.get(target, key);//把获取到的值return出去
        },
        set(target, key, value) {
            //首先要获取到dep
            const dep=getDep(target,key);
            //获取到更新的值
            const result=Reflect.set(target,key,value);
            //触发依赖:更新值以后再触发依赖
            dep.notice();
            return result;
        }
    });
}
// const user = reactive({
//     age: 18
// });
// let double;
// effectWatch(() => {
//     console.log("----reactivity----")
//     double=user.age;
//     console.log(double);
// });
// user.age=19;