深入浅出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的思路之后,发现实现响应性并没有想象中的困难啊,有了理论的加持,就该动手实践啦!加油!!

相关推荐
Hilaku4 分钟前
不要在简历上写精通 Vue3?来自面试官的真实劝退
前端·javascript·vue.js
三小河10 分钟前
前端视角详解 Agent Skill
前端·javascript·后端
颜酱23 分钟前
二叉树遍历思维实战
javascript·后端·算法
鹏多多27 分钟前
移动端H5项目,还需要react-fastclick解决300ms点击延迟吗?
前端·javascript·react.js
不想秃头的程序员38 分钟前
Vue3 封装 Axios 实战:从基础到生产级,新手也能秒上手
前端·javascript·面试
奔跑的web.1 小时前
UniApp 路由导航守
前端·javascript·uni-app
竟未曾年少轻狂1 小时前
Vue3 生命周期钩子
前端·javascript·vue.js·前端框架·生命周期
TT哇1 小时前
【实习】数字营销系统 银行经理端(interact_bank)前端 Vue 移动端页面的 UI 重构与优化
java·前端·vue.js·ui
不一样的少年_1 小时前
Chrome 插件实战:如何实现“杀不死”的可靠数据上报?
前端·javascript·监控
偶像佳沛1 小时前
JS 对象
前端·javascript