经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » Vue.js » 查看文章
源码分析Vue3响应式核心之effect
来源:jb51  时间:2023/4/24 8:51:15  对本文有异议

vue3响应式核心文章汇总:

vue3响应式核心之reactive源码详解

vue3响应式核心之effect源码详解

上篇vue3响应式核心之reactive源码详解详细讲解了reactive的原理和实现源码,本篇文章将讲解effect源码。

通常情况下我们是不会直接使用effect的,因为effect是一个底层的API,在我们使用Vue3的时候Vue默认会帮我们调用effect。 effect翻译为作用,意思是使其发生作用,这个使其的其就是我们传入的函数,所以effect的作用就是让我们传入的函数发生作用,也就是执行这个函数。 执行过程简图如下:

接下来先通过例子了解effect的基本用法,然后再去了解原理。

一、effect用法

1、基本用法

  1. const obj = reactive({count: 1})
  2.  
  3. const runner = effect(() => {
  4. console.log(obj.count)
  5. })
  6.  
  7. obj.count++

结果会先打印1, 然后在obj.count++之后打印出2。

流程简图如下:

运行effect(fun)

  1. // 先执行
  2. fun() // 打印出1
  3.  
  4. const runner = new ReactiveEffect(fn)
  5.  
  6. return runner
  7.  
  8. runner {
  9. run() {
  10. this.fun() //执行fun
  11. },
  12. stop() {
  13.  
  14. }
  15. }

console.log(obj.count)track依赖收集 结构如下:

obj.count++触发依赖,执行runner.run(), 实际运行的是

  1. () => {
  2. console.log(obj.count)
  3. }

所以又打印出2

2、lazy属性为true

此值为 true 时,只有在第一次手动调用 runner 后,依赖数据变更时,才会自动执行 effect 的回调,可以理解为 effect 的是在手动调用 runner 后才首次执行

  1. const obj = reactive({count: 1})
  2.  
  3. const runner = effect(() => {
  4. console.log(obj.count)
  5. }, {
  6. lazy: true
  7. })
  8. runner()
  9. obj.count++

只会打印出2

原因是effect源码中有如下逻辑:

3、options中包含onTrack

  1. let events = []
  2. const onTrack = (e) => {
  3. events.push(e)
  4. }
  5. const obj = reactive({ foo: 1, bar: 2 })
  6. const runner = effect(
  7. () => {
  8. console.log(obj.foo)
  9. },
  10. { onTrack }
  11. )
  12. console.log('runner', runner)
  13. obj.foo++
  14. console.log("events", events)

看下events的打印结果:

  1. [
  2. {
  3. effect: runner, // effect 函数的返回值
  4. target: toRaw(obj), // 表示的是哪个响应式数据发生了变化
  5. type: TrackOpTypes.GET, // 表示此次记录操作的类型。 get 表示获取值
  6. key: 'foo'
  7. }
  8. ]

二、源码分析

1、effect方法的实现

  1. // packages/reactivity/src/effect.ts
  2. export interface ReactiveEffectOptions extends DebuggerOptions {
  3. lazy?: boolean
  4. scheduler?: EffectScheduler
  5. scope?: EffectScope
  6. allowRecurse?: boolean
  7. onStop?: () => void
  8. }
  9.  
  10. export function effect<T = any>(
  11. fn: () => T, // 副作用函数
  12. options?: ReactiveEffectOptions // 结构如上
  13. ): ReactiveEffectRunner {
  14. // 如果 fn 对象上有 effect 属性
  15. if ((fn as ReactiveEffectRunner).effect) {
  16. // 那么就将 fn 替换为 fn.effect.fn
  17. fn = (fn as ReactiveEffectRunner).effect.fn
  18. }
  19. // 创建一个响应式副作用函数
  20. const _effect = new ReactiveEffect(fn)
  21. if (options) {
  22. // 将配置项合并到响应式副作用函数上
  23. extend(_effect, options)
  24. // 如果配置项中有 scope 属性(该属性的作用是指定副作用函数的作用域)
  25. if (options.scope) recordEffectScope(_effect, options.scope)
  26. }
  27. if (!options || !options.lazy) { // options.lazy 不为true
  28. _effect.run() // 执行响应式副作用函数 首次执行fn()
  29. }
  30. // _effect.run作用域绑定到_effect
  31. const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  32. // 将响应式副作用函数赋值给 runner.effect
  33. runner.effect = _effect
  34. return runner
  35. }

核心代码:

创建一个响应式副作用函数const _effect = new ReactiveEffect(fn),其运行结果如下:

非lazy状态执行响应式副作用函数_effect.run()

  1. if (!options || !options.lazy) { // options.lazy 不为true
  2. _effect.run() // 执行响应式副作用函数 首次执行fn()
  3. }

_effect.run作用域绑定到_effect

  1. // _effect.run作用域绑定到_effect
  2. const runner = _effect.run.bind(_effect) as ReactiveEffectRunner

返回副作用函数runner

2、ReactiveEffect函数源码

  1. export class ReactiveEffect<T = any> {
  2. active = true
  3. deps: Dep[] = [] // 响应式依赖项的集合
  4. parent: ReactiveEffect | undefined = undefined
  5.  
  6. /**
  7. * Can be attached after creation
  8. * @internal
  9. */
  10. computed?: ComputedRefImpl<T>
  11. /**
  12. * @internal
  13. */
  14. allowRecurse?: boolean
  15. /**
  16. * @internal
  17. */
  18. private deferStop?: boolean
  19.  
  20. onStop?: () => void
  21. // dev only
  22. onTrack?: (event: DebuggerEvent) => void
  23. // dev only
  24. onTrigger?: (event: DebuggerEvent) => void
  25.  
  26. constructor(
  27. public fn: () => T,
  28. public scheduler: EffectScheduler | null = null,
  29. scope?: EffectScope
  30. ) {
  31. // 记录当前 ReactiveEffect 对象的作用域
  32. recordEffectScope(this, scope)
  33. }
  34.  
  35. run() {
  36. // 如果当前 ReactiveEffect 对象不处于活动状态,直接返回 fn 的执行结果
  37. if (!this.active) {
  38. return this.fn()
  39. }
  40. // 寻找当前 ReactiveEffect 对象的最顶层的父级作用域
  41. let parent: ReactiveEffect | undefined = activeEffect
  42. let lastShouldTrack = shouldTrack // 是否要跟踪
  43. while (parent) {
  44. if (parent === this) {
  45. return
  46. }
  47. parent = parent.parent
  48. }
  49. try {
  50. // 记录父级作用域为当前活动的 ReactiveEffect 对象
  51. this.parent = activeEffect
  52. activeEffect = this // 将当前活动的 ReactiveEffect 对象设置为 “自己”
  53. shouldTrack = true // 将 shouldTrack 设置为 true (表示是否需要收集依赖)
  54. // effectTrackDepth 用于标识当前的 effect 调用栈的深度,执行一次 effect 就会将 effectTrackDepth 加 1
  55. trackOpBit = 1 << ++effectTrackDepth
  56.  
  57. if (effectTrackDepth <= maxMarkerBits) {
  58. // 初始依赖追踪标记
  59. initDepMarkers(this)
  60. } else {
  61. // 清除依赖追踪标记
  62. cleanupEffect(this)
  63. }
  64. // 返回副作用函数执行结果
  65. return this.fn()
  66. } finally {
  67. // 如果 effect调用栈的深度 没有超过阈值
  68. if (effectTrackDepth <= maxMarkerBits) {
  69. // 确定最终的依赖追踪标记
  70. finalizeDepMarkers(this)
  71. }
  72. // 执行完毕会将 effectTrackDepth 减 1
  73. trackOpBit = 1 << --effectTrackDepth
  74. // 执行完毕,将当前活动的 ReactiveEffect 对象设置为 “父级作用域”
  75. activeEffect = this.parent
  76. // 将 shouldTrack 设置为上一个值
  77. shouldTrack = lastShouldTrack
  78. // 将父级作用域设置为 undefined
  79. this.parent = undefined
  80. // 延时停止,这个标志是在 stop 方法中设置的
  81. if (this.deferStop) {
  82. this.stop()
  83. }
  84. }
  85. }
  86.  
  87. stop() {
  88. // stopped while running itself - defer the cleanup
  89. // 如果当前 活动的 ReactiveEffect 对象是 “自己”
  90. // 延迟停止,需要执行完当前的副作用函数之后再停止
  91. if (activeEffect === this) {
  92. // 在 run 方法中会判断 deferStop 的值,如果为 true,就会执行 stop 方法
  93. this.deferStop = true
  94. } else if (this.active) {// 如果当前 ReactiveEffect 对象处于活动状态
  95. cleanupEffect(this) // 清除所有的依赖追踪标记
  96. if (this.onStop) {
  97. this.onStop()
  98. }
  99. this.active = false // 将 active 设置为 false
  100. }
  101. }
  102. }
  • run方法的作用就是执行副作用函数,并且在执行副作用函数的过程中,会收集依赖;
  • stop方法的作用就是停止当前的ReactiveEffect对象,停止之后,就不会再收集依赖了;
  • activeEffect和this并不是每次都相等的,因为activeEffect会跟着调用栈的深度而变化,而this则是固定的;

三、依赖收集相关

1、如何触发依赖收集

在副作用函数中, obj.count就会触发依赖收集

  1. const runner = effect(() => {
  2. console.log(obj.count)
  3. })

触发的入口在get拦截器里面

  1. function createGetter(isReadonly = false, shallow = false) {
  2. // 闭包返回 get 拦截器方法
  3. return function get(target: Target, key: string | symbol, receiver: object) {
  4. // ...
  5. if (!isReadonly) {
  6. track(target, TrackOpTypes.GET, key)
  7. }
  8. // ...
  9. }

2、track源码

  1. const targetMap = new WeakMap();
  2. /**
  3. * 收集依赖
  4. * @param target target 触发依赖的对象,例子中的obj
  5. * @param type 操作类型 比如obj.count就是get
  6. * @param key 指向对象的key, 比如obj.count就是count
  7. */
  8. export function track(target: object, type: TrackOpTypes, key: unknown) {
  9. if (shouldTrack && activeEffect) { // 是否应该依赖收集 & 当前的new ReactiveEffect()即指向的就是当前正在执行的副作用函数
  10.  
  11. // 如果 targetMap 中没有 target,就会创建一个 Map
  12. let depsMap = targetMap.get(target)
  13. if (!depsMap) {
  14. targetMap.set(target, (depsMap = new Map()))
  15. }
  16. let dep = depsMap.get(key)
  17. if (!dep) {
  18. depsMap.set(key, (dep = createDep())) // createDep 生成dep = { w:0, n: 0}
  19. }
  20.  
  21. const eventInfo = __DEV__
  22. ? { effect: activeEffect, target, type, key }
  23. : undefined
  24.  
  25. trackEffects(dep, eventInfo)
  26. }
  27. }

shouldTrack在上面也讲过,它的作用就是控制是否收集依赖;

activeEffect就是我们刚刚讲的ReactiveEffect对象,它指向的就是当前正在执行的副作用函数;

track方法的作用就是收集依赖,它的实现非常简单,就是在targetMap中记录下target和key;

targetMap是一个WeakMap,它的键是target,值是一个Map,这个Map的键是key,值是一个Set;

targetMap的结构伪代码如下:

  1. targetMap = {
  2. target: {
  3. key: dep
  4. },
  5. // 比如:
  6. obj: {
  7. count: {
  8. w: 0,
  9. n: 0
  10. }
  11. }
  12. }

以上是最原始的depMap

dev环境为增加响应式调试会增加eventInfo

  1. const eventInfo = __DEV__
  2. ? { effect: activeEffect, target, type, key }
  3. : undefined

eventInfo结构如下:

trackEffects(dep, eventInfo)

如果 dep 中没有当前的 ReactiveEffect 对象,就会添加进去, 作用就把对象的属性操作与副作用函数建立关联,接下来看trackEffects

3、trackEffects(dep, eventInfo)源码解读

  1. export function trackEffects(
  2. dep: Dep,
  3. debuggerEventExtraInfo?: DebuggerEventExtraInfo
  4. ) {
  5. let shouldTrack = false
  6. if (effectTrackDepth <= maxMarkerBits) {
  7. if (!newTracked(dep)) {
  8. // 执行之前 dep = Set(0) {w: 0, n: 0}
  9.  
  10. // 执行之后 dep = Set(0) {w: 0, n: 2}
  11. dep.n |= trackOpBit // set newly tracked
  12. shouldTrack = !wasTracked(dep)
  13. }
  14. } else {
  15. // Full cleanup mode.
  16. shouldTrack = !dep.has(activeEffect!)
  17. }
  18.  
  19. if (shouldTrack) {
  20. // 将activeEffect添加到dep
  21. dep.add(activeEffect!)
  22. activeEffect!.deps.push(dep)
  23. if (__DEV__ && activeEffect!.onTrack) { // onTrack逻辑
  24. activeEffect!.onTrack(
  25. extend(
  26. {
  27. effect: activeEffect!
  28. },
  29. debuggerEventExtraInfo!
  30. )
  31. )
  32. }
  33. }
  34. }

dep.add(activeEffect!) 如果 dep 中没有当前的 ReactiveEffect 对象,就会添加进去

最终生成的depTarget结构如下:

四、触发依赖

比如例子中代码obj.count++就会触发set拦截,触发依赖更新

  1. function createSetter(shallow = false) {
  2. return function set(
  3. target: object,
  4. key: string | symbol,
  5. value: unknown,
  6. receiver: object
  7. ): boolean {
  8. //...
  9. const result = Reflect.set(target, key, value, receiver)
  10. // don't trigger if target is something up in the prototype chain of original
  11. if (target === toRaw(receiver)) {
  12. if (!hadKey) {
  13. trigger(target, TriggerOpTypes.ADD, key, value) // 触发ADD依赖更新
  14. } else if (hasChanged(value, oldValue)) {
  15. trigger(target, TriggerOpTypes.SET, key, value, oldValue) //触发SET依赖更新
  16. }
  17. }
  18. //...
  19. }

1、trigger依赖更新

  1. // 路径:packages/reactivity/src/effect.ts
  2. export function trigger(
  3. target: object,
  4. type: TriggerOpTypes,
  5. key?: unknown,
  6. newValue?: unknown,
  7. oldValue?: unknown,
  8. oldTarget?: Map<unknown, unknown> | Set<unknown>
  9. ) {
  10. const depsMap = targetMap.get(target) // 获取depsMap, targetMap是在track中创建的依赖
  11. if (!depsMap) {
  12. // never been tracked
  13. return
  14. }
  15.  
  16. let deps: (Dep | undefined)[] = []
  17. if (type === TriggerOpTypes.CLEAR) {
  18. // collection being cleared
  19. // trigger all effects for target
  20. deps = [...depsMap.values()]
  21. } else if (key === 'length' && isArray(target)) {
  22. const newLength = Number(newValue)
  23. depsMap.forEach((dep, key) => {
  24. if (key === 'length' || key >= newLength) {
  25. deps.push(dep)
  26. }
  27. })
  28. } else {
  29. // schedule runs for SET | ADD | DELETE
  30. if (key !== void 0) {
  31. deps.push(depsMap.get(key))
  32. }
  33.  
  34. // also run for iteration key on ADD | DELETE | Map.SET
  35. switch (type) {
  36. case TriggerOpTypes.ADD:
  37. if (!isArray(target)) {
  38. deps.push(depsMap.get(ITERATE_KEY))
  39. if (isMap(target)) {
  40. deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
  41. }
  42. } else if (isIntegerKey(key)) {
  43. // new index added to array -> length changes
  44. deps.push(depsMap.get('length'))
  45. }
  46. break
  47. case TriggerOpTypes.DELETE:
  48. if (!isArray(target)) {
  49. deps.push(depsMap.get(ITERATE_KEY))
  50. if (isMap(target)) {
  51. deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
  52. }
  53. }
  54. break
  55. case TriggerOpTypes.SET:
  56. if (isMap(target)) {
  57. deps.push(depsMap.get(ITERATE_KEY))
  58. }
  59. break
  60. }
  61. }
  62.  
  63. const eventInfo = __DEV__
  64. ? { target, type, key, newValue, oldValue, oldTarget }
  65. : undefined
  66.  
  67. if (deps.length === 1) {
  68. if (deps[0]) {
  69. if (__DEV__) {
  70. triggerEffects(deps[0], eventInfo)
  71. } else {
  72. triggerEffects(deps[0])
  73. }
  74. }
  75. } else {
  76. const effects: ReactiveEffect[] = []
  77. for (const dep of deps) {
  78. if (dep) {
  79. effects.push(...dep)
  80. }
  81. }
  82. if (__DEV__) {
  83. triggerEffects(createDep(effects), eventInfo)
  84. } else {
  85. triggerEffects(createDep(effects))
  86. }
  87. }
  88. }

const depsMap = targetMap.get(target) 获取 targetMap 中的 depsMap targetMap结构如下:

执行以上语句之后的depsMap结构如下:

将 depsMap 中 key 对应的 ReactiveEffect 对象添加到 deps 中deps.push(depsMap.get(key))之后的deps结构如下:

triggerEffects(deps[0], eventInfo)

  1. const eventInfo = __DEV__
  2. ? { target, type, key, newValue, oldValue, oldTarget }
  3. : undefined
  4. if (deps.length === 1) {
  5. if (deps[0]) {
  6. if (__DEV__) {
  7. triggerEffects(deps[0], eventInfo)
  8. } else {
  9. triggerEffects(deps[0])
  10. }
  11. }
  12. }

trigger函数的作用就是触发依赖,当我们修改数据的时候,就会触发依赖,然后执行依赖中的副作用函数。

在这里的实现其实并没有执行,主要是收集一些需要执行的副作用函数,然后在丢给triggerEffects函数去执行,接下来看看triggerEffects函数。

2、triggerEffects(deps[0], eventInfo)

  1. export function triggerEffects(
  2. dep: Dep | ReactiveEffect[],
  3. debuggerEventExtraInfo?: DebuggerEventExtraInfo
  4. ) {
  5. // spread into array for stabilization
  6. const effects = isArray(dep) ? dep : [...dep]
  7. for (const effect of effects) {
  8. if (effect.computed) {
  9. triggerEffect(effect, debuggerEventExtraInfo)
  10. }
  11. }
  12. for (const effect of effects) {
  13. if (!effect.computed) {
  14. triggerEffect(effect, debuggerEventExtraInfo)
  15. }
  16. }
  17. }

主要步骤

const effects = isArray(dep) ? dep : [...dep]获取effects

triggerEffect(effect, debuggerEventExtraInfo)执行effect,接下来看看源码

3、triggerEffect(effect, debuggerEventExtraInfo)

  1. function triggerEffect(
  2. effect: ReactiveEffect,
  3. debuggerEventExtraInfo?: DebuggerEventExtraInfo
  4. ) {
  5. if (effect !== activeEffect || effect.allowRecurse) {
  6. // 如果 effect.onTrigger 存在,就会执行,只有开发模式下才会执行
  7. if (__DEV__ && effect.onTrigger) {
  8. effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
  9. }
  10. // 如果 effect 是一个调度器,就会执行 scheduler
  11. if (effect.scheduler) {
  12. effect.scheduler()
  13. } else {
  14. // 其它情况执行 effect.run()
  15. effect.run()
  16. }
  17. }
  18. }

effect.run()就是执行副作用函数

以上就是源码分析Vue3响应式核心之effect的详细内容,更多关于Vue3 effect的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号