vue3响应式原理初探一

101 阅读2分钟

今日初探两个函数

  • reactive
  • effect

reactive

Proxy创建代理式对象,在get和set里面做一些相关操作,比如get里面取的值是对象时,将取出的对象继续代理,如: `

get(target, prop) {
    const result = Reflect.get(target, prop)
    if (isObj(result)) { // 是对象,继续创建响应式
        return reactive(result)
    }
    return Reflect.get(target, prop)
},

测试例子及效果:

const {reactive} = VueCore
const orgin = {
    age: 18,
    name: 'lyh',
    obj: {
        test: '111'
    }
}
const state = reactive(orgin)
console.log(state.obj)

是对象不继续代理的打印效果:

企业微信截图_16756698822754.png

继续代理的效果:

企业微信截图_16756699842392.png

可以看到obj属性也被代理了

effect

effect是响应式的核心,先看看用法:

effect(() => {
    document.body.innerText = state.age
})
setTimeout(() => {
    state.age = 19
}, 2000)

age初始是18,两秒后会重新渲染成19。看用法,我们想要的是在每次set的时候,触发effect函数执行,那怎么能触发到effect呢,我们在effect初始化的时候,把effect存起来,等set的时候,取出来执行。

简易实现:

export let bukect = []
export const effect = (fn: Function) => {
    bukect.push(fn) // 将要执行的函数存起来
    fn()
}

设置值的时候,将函数取出来执行:

export const handler = {
    //...
    set(target, prop, value) {
        const result =  Reflect.set(target,prop, value );
        bukect.forEach(fn => {
            fn() // 取出来执行
        })
        return result
    }
};
这里已经能实现age改变触发页面更新了,但是有如下问题:
  1. effect里面只获取了age的值,如果我们改变name属性,也会触发effect再次执行,name没有在effect里面,我们希望不要执行
  2. 如果写两个effect,一个里面获取age,一个里面获取name,我们希望初始化时执行两个effect,渲染age和name,过一秒改变age,执行获取age的effect,而事实一秒后两个effect都执行了
分析问题:

从上面可以看出,只要初始化effect,就会往bukect存入函数,只要set,就会取出来执行。出问题的原因是effect和和数据源和属性没有关联,我们用一种数据结构把他们关联起来:

企业微信截图_16756733105764.png

这样我们可以通过target[key]取到具体要执行的函数,改写代理代码:

export const handler = {
    get(target, prop) {
        const result = Reflect.get(target, prop)
        if (isObj(result)) {
            return reactive(result)
        }
        if (!activeEffect) return Reflect.get(target, prop) // 没有effect,直接返回值
        let depMaps = bukect.get(target) // map: prop >> effect
        if (!depMaps) {
            depMaps = new Map() // map: prop >> effect
            bukect.set(target, depMaps)
        }
        let deps = depMaps.get(prop) // effect
       if (!deps) { // 没有effect列表,新建一个
           depMaps.set(prop, deps = new Set())
       }
        deps.add(activeEffect)
        return Reflect.get(target, prop)
    },
    set(target, prop, value) {
        const result =  Reflect.set(target,prop, value );
        const depMaps = bukect.get(target) // map: prop >> effect
        if (depMaps) {
            const deps = depMaps.get(prop) // set: effect,根据target和prop取出的effect函数
            deps && deps.forEach(fn => {
                fn()
            })
        }
        return result
    }
};

上述第一个问题解决,第二个问题下次再探索 仓库地址, 欢迎讨论