浅理解Vue响应式基础 effect()

127 阅读3分钟

什么是响应式?

响应式应该是指当页面以来的数据发生变化时,页面要做到自动更新内容。也就是标签的innerHTML或textContet等属性要获取到所绑定的属性的最新值。

effect函数

我们都知道Vue3内部是通过 Proxy 代理引用数据类型的,因此我们可以在对被代理对象的属性执行特定操作前先触发副作用函数(回调函数)。而这些副作用函数又从何处获取呢?这时就要用到 effect 函数。

副作用函数与响应式数据之间的关联关系

graph TD
Weakmap --> data-被代理的原始对象
Weakmap --> Map
Map --> key-属性
Map --> Set-副作用函数

绑定原始对象是为了能够触发所有绑定的副作用函数,即使多个代理对象同代理同一个原始对象。

effect实现

let activeEffect // 全局变量存储当前激活的effectFn副作用函数
const effectStack = [] // effect栈,用于解决 effectFn副作用函数嵌套问题。
function effect(fn){
    // 除了第一次声明时会执行,其他都是代理对象的属性值更改时 effectFn 才会被执行
    const effectFn = ()=>{
        cleanup(effectFn) // 执行副作用函数时先将当前副作用函数从effectFn.deps中移除,避免不必要的触发。如果代理对象的属性被重新读取时,该副作用函数会重新绑定到对应属性下的set集合中,以便更改属性值时会触发该副作用函数。
        activeEffect = effectFn
        // 在调用effectFn副作用函数前,先将其保存到栈中
        effectStack.push(effectFn)
        fn()
        // 执行完之后再将已执行的副作用函数移出栈
        effectStack.pop()
        // 还原activeEffect指向上一层的副作用函数
        activeEffect = effectStack[effectStack.length-1]
    }
    effectFn.deps = [] // 存放的是Set()类型,包含所有属性中含有该副作用函数的set
    effectFn()
}
// 从所有被代理对象的属性所关联的Set中移除该副作用函数。如果某个属性不再被读取,则更改该属性值时不会触发副作用函数
// 多个属性可以绑定同一个副作用函数,key-Set Set中存放副作用函数。
// 
function cleanup(effectFn){
    for(let i=0; i<effectFn.deps.length; i++){
        const deps = effectFn.deps[i]
        deps.delete(effectFn)
    }
    effectFn.deps.length = 0
}

样例1:

const data = {ok:true,text:'hello'}
const proxyObj = new Proxy(data,{....})
effect(()=>{
	document.body.innerText = proxyObj.ok ? proxyObj.text : 'not'
})
// 当读取proxyObj对象中的属性时,activeEffect会指向上面的箭头函数,并将该副作用函数保存到对应属性匹配的Set集合中。
// proxyObj中的两个属性都会绑定该副作用函数。所以当没有执行cleanup()时,其中任意属性值修改时都会触发副作用函数
// 而当有了cleanup()函数后,每次调用副作用函数都会将该副作用函数从每个属性的Set集合中移除。只有当再次调用该属性值时才会重新绑定。
// 上述proxyObj.ok的值修改为false时会触发副作用函数,然后执行cleanup()导致 proxyObj.text绑定的副作用函数会被删除,而 proxyObj.text又不会被再次读取,所以当修改proxyObj.text的值时不会触发之前绑定的副作用函数。