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'

这样便达到预期!!

相关推荐
Midsummer3 分钟前
nuxt安装报错-网络问题
vue.js·nuxt.js
张童瑶3 分钟前
Vue Electron 使用来给若依系统打包成exe程序,出现登录成功但是不跳转页面(已解决)
javascript·vue.js·electron
火柴就是我15 分钟前
每日见闻之<script type="module"> 的含义与作用
javascript
Catfood_Eason17 分钟前
HTML5 盒子模型
前端·html
小李小李不讲道理23 分钟前
「Ant Design 组件库探索」二:Tag组件
前端·react.js·ant design
1024小神27 分钟前
在rust中执行命令行输出中文乱码解决办法
前端·javascript
wordbaby28 分钟前
React Router v7 中的 `Layout` 组件工作原理
前端·react.js
旺仔牛仔QQ糖29 分钟前
Vue为普通函数添加防抖功能(基于Pinia 插件为action 配置防抖功能 引发思考)
前端·vue.js
lyc23333334 分钟前
鸿蒙Next人脸比对技术:轻量化模型的智能应用
前端
*小雪39 分钟前
vue2使用vue-cli脚手架搭建打包加密方法-JavaScript obfuscator
前端·javascript·vue.js