Vue3响应式系统(一)https://blog.csdn.net/qq_55806761/article/details/135587077
六、嵌套的effect与effect栈。
什么场景会用到effect嵌套呢?听我娓娓道来。
就用Vue.js来说吧,Vue.js的渲染函数就是在effect中执行的:
/*Foo组件*/
const Foo = {render() {return /*.....*/}
}// effect中执行Foo组件中的渲染函数
effect(() => {Foo.render()
})
当组件发生嵌套的时候,渲染的时候effect函数中就会发生effect嵌套。
//Bar组件
const Bar = {render() {/* ... */}
}
const Foo = {render() {return <Bar />}
}//effect执行的时候就会发生嵌套
effect(() => {Foo.render()effect(() => {Bar.render()})
})
所以,目前并不能支持effect嵌套,我们用之前的代码来测试一下:
//原始数据
const data = {ok: true,text: 'hello world'
}
// WeakMap桶
const bucketMap = new WeakMap()
//用全局变量存储被注册的副作用函数
let activeEffectfunction effect(fn) {const effectFn = () => {cleanup(effectFn)activeEffect = effectFnfn()}// deps用来存储所有与这副作用函数相关联的依赖集合effectFn.deps = []effectFn()
}
const obj = new Proxy(data, {get(target, key) {// 注册副作用函数track(target, key)return target[key]},set(target, key, newVal) {target[key] = newVal// 触发副作用函数trigger(target, key)return true}
})
function cleanup(effectFn) {//遍历effectFn的deps数组for(let i = 0; i < effectFn.deps.length; i++) {let deps = effectFn.deps[i]deps.delete(effectFn)}// 最后需要重置effectFn.deps数组effectFn.deps.length = 0
}
function trigger(target, key) {// 取targetconst depsMap = bucketMap.get(target)if (!depsMap) return// 根据key取副作用函数const effects = depsMap.get(key)// 执行副作用函数const effectToRun = new Set(effects)effectToRun && effectToRun.forEach(fn => fn())
}
function track(target, key) {// 没有activeEffect直接返回if (!activeEffect) return target[key]// 取出WeakMap桶里的值 target ===> keylet depsMap = bucketMap.get(target)// 如果不存在depsMap,那就新建Map与target建立联系if (!depsMap) {bucketMap.set(target, (depsMap = new Map()))}// key ===> effectFnlet deps = depsMap.get(key)if (!deps) {depsMap.set(key, deps = new Set())}// 注册副作用函数deps.add(activeEffect)// ======= 主要就是增加关联数组中 ===========activeEffect.deps.push(deps)
}let temp1, temp2
effect(function effectFn1() {console.log('effectFn1执行')effect(function effectFn2() {console.log('effectFn2执行');temp2 = obj.ok})temp1 = obj.text
})
obj.text = 'Vue3'
出现问题了,当我们修改text的值的时候,我们希望的是触发effectFn1,而现在是触发effectFn2,并没有执行effectFn1。
问题在哪里呢?
我们用全局变量activeEffect来存储通过effect函数注册的副作用函数,意味着同一时刻activeEffect所存储的副作用函数只能有一个。当副作用函数发生嵌套时,内层副作用函数会覆盖activeEffect,并且永远不会恢复,即使响应式数据是在外层副作用函数中读取的,他们收集到的副作用函数也都会是内层副作用函数,这个就是问题所在。
为了解决这个问题,我们需要一个副作用函数栈effectStack,在副作用函数执行的时候,将当前副作用函数压入栈中,执行完毕后将副作用函数弹出,activeEffect始终指向栈顶的副作用函数。这样便不会出现相互影响的情况了。
增加副作用函数栈
//用全局变量存储被注册的副作用函数
let activeEffect
// effectStack栈
let effectStack = []
function effect(fn) {const effectFn = () => {cleanup(effectFn)activeEffect = effectFneffectStack.push(effectFn)fn()effectStack.pop()activeEffect = effectStack[effectStack.length - 1]}// deps用来存储所有与这副作用函数相关联的依赖集合effectFn.deps = []effectFn()
}
完整代码:
//原始数据
const data = {ok: true,text: 'hello world'
}
// WeakMap桶
const bucketMap = new WeakMap()
//用全局变量存储被注册的副作用函数
let activeEffect
// effectStack栈
let effectStack = []
function effect(fn) {const effectFn = () => {cleanup(effectFn)activeEffect = effectFneffectStack.push(effectFn)fn()effectStack.pop()activeEffect = effectStack[effectStack.length - 1]}// deps用来存储所有与这副作用函数相关联的依赖集合effectFn.deps = []effectFn()
}
const obj = new Proxy(data, {get(target, key) {// 注册副作用函数track(target, key)return target[key]},set(target, key, newVal) {target[key] = newVal// 触发副作用函数trigger(target, key)return true}
})
function cleanup(effectFn) {//遍历effectFn的deps数组for(let i = 0; i < effectFn.deps.length; i++) {let deps = effectFn.deps[i]deps.delete(effectFn)}// 最后需要重置effectFn.deps数组effectFn.deps.length = 0
}
function trigger(target, key) {// 取targetconst depsMap = bucketMap.get(target)if (!depsMap) return// 根据key取副作用函数const effects = depsMap.get(key)// 执行副作用函数const effectToRun = new Set(effects)effectToRun && effectToRun.forEach(effectFn => effectFn())
}
function track(target, key) {// 没有activeEffect直接返回if (!activeEffect) return target[key]// 取出WeakMap桶里的值 target ===> keylet depsMap = bucketMap.get(target)// 如果不存在depsMap,那就新建Map与target建立联系if (!depsMap) {bucketMap.set(target, (depsMap = new Map()))}// key ===> effectFnlet deps = depsMap.get(key)if (!deps) {depsMap.set(key, deps = new Set())}// 注册副作用函数deps.add(activeEffect)// ======= 主要就是增加关联数组中 ===========activeEffect.deps.push(deps)
}let temp1, temp2
effect(function effectFn1() {debuggerconsole.log('effectFn1执行')effect(function effectFn2() {console.log('effectFn2执行');temp2 = obj.ok})temp1 = obj.text
})
obj.text = 'Vue3'
这样便达到预期!!
Vue3响应式系统(三)https://blog.csdn.net/qq_55806761/article/details/135635322