实现响应式系统--解决分支切换的问题

48 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 16 天,点击查看活动详情 响应式数据的基本实现

什么是分支切换

假如遇到了如下问题,obj.text 的显示依赖于obj.haveData的值来确定。 我们将这种因为obj.haveData变化,执行的代码分支跟着变化的情况叫分支切换


effect(() => {
    console.warn('副用用函数执行====')
    document.body.innerText = obj.haveData ? obj.text : 'no data'
})

按照上一节数据结构来

{obj对象: 
    { haveData: {effectFn}, 
      text: {effectFn }
    }
}

据此可以看到haveDatatext的副作用函数都是effectFn 由此当haveData值为false执行副用用函数effectFn后,依赖关系就变了,因为没用到obj.text,而当obj.text变化后仍旧会执行effectFn函数,可是此时obj.text压根就用不到了。从而导致了不必要的更新。

造成这种现象的主要原因是,当obj.haveData变成false的时候。依赖关系已经变了,text对应的依赖已经不存在了

第一次执行

    document.body.innerText = obj.haveData ? obj.text : 'no data'

调用了两次get函数 key分别是haveData 以及text,

target ---> haveData -----> effectFn

target ---> text -----> effectFn

解决方法

解决起来当更新依赖就好了。

obj.haveData = false -->调用set方法 --> 执行副作用函数 --> 清空所有依赖关系 --> 副作用函数调用get方法obj.haveData ? obj.text : 'no data' --> 重新收集依赖

let activeEffect
function effect(fn) {
    const effectFn = () => {
        cleanup(effectFn)
        activeEffect = effectFn
        fn()
    }
    effectFn.deps = []
    effectFn()
}

function cleanup(effectFn) {
    for (let index = 0; index < effectFn.deps.length; index++) {
        const deps = effectFn.deps[index];
        deps.delete(effectFn)
    }
    effectFn.deps.length = 0
}


let effectFuncList = new WeakMap()
const data = { text: 'hello world', ok: true }
const obj = new Proxy(data, {
    get(target, key) {
        if (!activeEffect) return target[key]
        // 是不是根据key来保存就能够保证唯一呢。
        let keyMap = effectFuncList.get(target)
        if (!keyMap) effectFuncList.set(target, (keyMap = new Map()))
        let deps = keyMap.get(key)
        if (!deps) keyMap.set(key, (deps = new Set()))
        deps.add(activeEffect)
        activeEffect.deps.push(deps)
        return target[key]
    },
    set(target, key, newValue) {
        target[key] = newValue
        // 根据target获取以key为键的集合
        const keyMap = effectFuncList.get(target)
        if (!keyMap) return

        // 根据target获取以key为键的集合
        const effectsSet = keyMap.get(key)
        if (!effectsSet) return
        // 执行副作用函数
        const effectsToRun = new Set(effectsSet)
        effectsToRun.forEach(fn => fn())
    }
})





effect(() => {
    console.warn('副用用函数执行====')
    document.body.innerText = obj.ok ? obj.text : 'no data'
})



setTimeout(() => {
    obj.ok = false
}, 1000);

 setTimeout(() => {
     obj.text = 'hello vue'
 }, 3000);


开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 16 天,点击查看活动详情 响应式数据的基本实现