深入浅出Vue的响应性(下篇)

深入浅出Vue的响应性(上篇)运行完了effect,接下来一段setTimeout代码,下篇会大家一起探究setter过程都做了什么事。

上图这一段代码,Vue究竟做了什么事呢?大概有以下三件:

  1. 创建proxy;
  2. 收集effect的依赖;
  3. 触发收集的依赖;

接下来是分析setter过程,下图是setter调用流程图,能帮助大家理性思路;

触发setter行为会影响数据变化,触发视图的更新;触发setter方法,进入createSetter方法:

js 复制代码
function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    // 此时的result是true
    const result = Reflect.set(target, key, value, receiver)
   
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

set中传入的target是{name: '张三'},key是name,value是'李四', receiver是{name: '张三'};

第15行触发了trigger方法,上文提到了响应性的核心其实是依赖收集和依赖触发;上篇文章里说过track方法是响应性的核心方法,它是用来触发依赖收集的;这篇文章里trigger类似于track方法,不同的是trigger是用来依赖触发的;

下面是trigger方法:

js 复制代码
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }

  let deps: (Dep | undefined)[] = []
  if () {
    // 省略
  } else {
    if (key !== void 0) {
      // key是"name",depsMap.get(key)是set,里面有ReactiveEffect
      deps.push(depsMap.get(key))
    }
    // 此时的type是"set"
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  if (deps.length === 1) {
    if (deps[0]) {
      if (__DEV__) {
        // 核心的依赖触发方法
        triggerEffects(deps[0], eventInfo)
      } else {
        triggerEffects(deps[0])
      }
    }
  }
}

第9行的targetMap此时如下图,因为targetMap是有值的,所以depsMap是 Map(1) {'name' => Set(1)};

第21行,key是"name",depsMap.get(key)是set,里面有ReactiveEffect,所以deps存放了set; 第24行,这里的type是set, switch语句就会走set的逻辑;

第45行,判断target是不是map,此时的target是{name: '李四'}对象,所以就直接break;

triggerEffects方法:

js 复制代码
export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  const effects = isArray(dep) ? dep : [...dep]
  for (const effect of effects) {
    if (!effect.computed) {
      // 用triggerEffect触发依赖;
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
}

第5行,此时的dep就是Set(1) {ReactiveEffect}

第6行,此时的effects是[ReactiveEffect],ReactiveEffect的数组,它的fn还是我们之前的document.querySelector('#app').innerText = obj.name

第9行,用triggerEffect触发依赖; 接着我们看一下triggerEffect方法:

js 复制代码
function triggerEffect(
  effect: ReactiveEffect,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  if (effect !== activeEffect || effect.allowRecurse) {
    if (effect.scheduler) {
      effect.scheduler()
    } else {
      effect.run()
    }
  }
}

第9行,执行了effect.run方法,effect是reactiveEffect的实例,本质上是触发fn函数,也就是我们传入的() => { document.querySelector('#app').innerText = obj.name };此时的obj.name已经从张三变成了李四,执行fn是把李四渲染到页面;

到这里setter的过程就结束了;

整个setter流程主要是做了两件事:

  1. 修改obj的值;
  2. 触发targetMap下保存的fn函数;

理清getter和setter的思路之后,发现实现响应性并没有想象中的困难啊,有了理论的加持,就该动手实践啦!加油!!

相关推荐
ohMyGod_12344 分钟前
React16,17,18,19新特性更新对比
前端·javascript·react.js
前端小趴菜051 小时前
React-forwardRef-useImperativeHandle
前端·vue.js·react.js
@大迁世界1 小时前
第1章 React组件开发基础
前端·javascript·react.js·前端框架·ecmascript
Hilaku1 小时前
从一个实战项目,看懂 `new DataTransfer()` 的三大妙用
前端·javascript·jquery
爱分享的程序员1 小时前
前端面试专栏-算法篇:20. 贪心算法与动态规划入门
前端·javascript·node.js
我想说一句1 小时前
事件委托与合成事件:前端性能优化的"偷懒"艺术
前端·javascript
爱泡脚的鸡腿1 小时前
Web第二次笔记
前端·javascript
Dream耀1 小时前
React合成事件揭秘:高效事件处理的幕后机制
前端·javascript
P7Dreamer1 小时前
Vue 3 + Element Plus 实现可定制的动态表格列配置组件
前端·vue.js
洋流1 小时前
0基础进大厂,第12天:ES6语法基础篇
javascript