Vue3响应式系统(二)

Vue响应式系统(一)

六、嵌套的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 activeEffect

function effect(fn) {
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn
    fn()
  }
  // 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) {
  // 取target
  const 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 ===> key
  let depsMap = bucketMap.get(target)
  // 如果不存在depsMap,那就新建Map与target建立联系
  if (!depsMap) {
    bucketMap.set(target, (depsMap = new Map()))
  }
  // key ===> effectFn
  let 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 = effectFn
    effectStack.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 = effectFn
    effectStack.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) {
  // 取target
  const 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 ===> key
  let depsMap = bucketMap.get(target)
  // 如果不存在depsMap,那就新建Map与target建立联系
  if (!depsMap) {
    bucketMap.set(target, (depsMap = new Map()))
  }
  // key ===> effectFn
  let 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() {
  debugger
  console.log('effectFn1执行')
  effect(function effectFn2() {
    console.log('effectFn2执行');
    temp2 = obj.ok
  })
  temp1 = obj.text
})
obj.text = 'Vue3'

这样便达到预期!!

相关推荐
web守墓人37 分钟前
【前端】ikun-markdown: 纯js实现markdown到富文本html的转换库
前端·javascript·html
Savior`L43 分钟前
CSS知识复习5
前端·css
许白掰44 分钟前
Linux入门篇学习——Linux 工具之 make 工具和 makefile 文件
linux·运维·服务器·前端·学习·编辑器
中微子5 小时前
🔥 React Context 面试必考!从源码到实战的完整攻略 | 99%的人都不知道的性能陷阱
前端·react.js
秋田君6 小时前
深入理解JavaScript设计模式之命令模式
javascript·设计模式·命令模式
中微子6 小时前
React 状态管理 源码深度解析
前端·react.js
风吹落叶花飘荡7 小时前
2025 Next.js项目提前编译并在服务器
服务器·开发语言·javascript
加减法原则7 小时前
Vue3 组合式函数:让你的代码复用如丝般顺滑
前端·vue.js
yanlele8 小时前
我用爬虫抓取了 25 年 6 月掘金热门面试文章
前端·javascript·面试
lichenyang4538 小时前
React移动端开发项目优化
前端·react.js·前端框架