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

相关推荐
wifi歪f4 小时前
🎉 Stenciljs,一个Web Components框架新体验
前端·javascript
不爱说话郭德纲5 小时前
👩‍💼产品姐一句小优化,让我给上百个列表加上一个动态实时计算高度的方法😿😿
前端·vue.js·性能优化
知识分享小能手5 小时前
React学习教程,从入门到精通, React教程:构建你的第一个 React 应用(1)
前端·javascript·vue.js·学习·react.js·ajax·前端框架
李剑一5 小时前
别乱封装,你看出事儿了吧...
前端·vue.js
gongzemin5 小时前
Vue 项目权限管理 路由 按钮权限
前端·vue.js
Flame_5 小时前
一行代码搞定Vue3异步请求:vue3-request让状态管理从地狱到天堂
vue.js
FAIRY_STARS5 小时前
VUE3大屏自适应布局
vue.js
GISBox6 小时前
GIS新手入门首选!GISBox中文界面+一键安装,零依赖轻松搞定三维数据发布
vue.js·json·gis
tianchang6 小时前
JS 排序神器 sort 的正确打开方式
前端·javascript·算法
FliPPeDround6 小时前
🚀 定义即路由:definePage宏如何让uni-app路由配置原地起飞?
前端·vue.js·uni-app