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'

这样便达到预期!!

相关推荐
Zheng1131 小时前
【可视化大屏】将柱状图引入到html页面中
javascript·ajax·html
程序猿小D1 小时前
第二百六十七节 JPA教程 - JPA查询AND条件示例
java·开发语言·前端·数据库·windows·python·jpa
john_hjy1 小时前
【无标题】
javascript
奔跑吧邓邓子1 小时前
npm包管理深度探索:从基础到进阶全面教程!
前端·npm·node.js
软件开发技术深度爱好者2 小时前
用HTML5+CSS+JavaScript庆祝国庆
javascript·css·html5
前端李易安2 小时前
ajax的原理,使用场景以及如何实现
前端·ajax·okhttp
汪子熙2 小时前
Angular 服务器端应用 ng-state tag 的作用介绍
前端·javascript·angular.js
杨荧2 小时前
【JAVA开源】基于Vue和SpringBoot的旅游管理系统
java·vue.js·spring boot·spring cloud·开源·旅游
Envyᥫᩣ2 小时前
《ASP.NET Web Forms 实现视频点赞功能的完整示例》
前端·asp.net·音视频·视频点赞
Мартин.6 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss