likes
comments
collection
share

干货说Vue3响应式实现

作者站长头像
站长
· 阅读数 35

这个问题怎么问?

关键词: 响应式、实时更新、双向绑定

  1. Vue3是怎么实现响应式的?
  2. 谈一谈对双向绑定的理解?

一. 常见问题

  1. 为什么vue3要用proxy,而不是继续使用Object.defineProperty

    1. Object.defineProperty 不能监听到对象的新增和删除。
    2. Object.defineProperty 需要给对象每一个属性一个一个预先设置好,这就需要最开始递归所有属性,有性能负担。

二. Reactive API

主要就是四步:判断是否是对象,判断是否白名单,保证响应式实现为单例,为对象创建响应式;

干货说Vue3响应式实现

其中白名单的数据类型:object、array、map、weakmap、set、weakset

三. Proxy get

Proxy get 的实现有三个重点:

  1. 为什么要对数组的查找方法进行包装?

    1. 设想一下,如果不进行包装,一个计算属性(computed effect)依赖了某个响应式数组的indexOf()的查找结果,proxy是这个数组的代理,当计算属性执行时,没有访问到该数组的任何一个成员,这就导致了该数组的任何一个成员都没有将computed effect作为依赖收集,那么当数组成员发生变化的时候,computed effect自然就不会得到执行,这显然不是我们的预期结果,因为我们期望让computed effect依赖于数组成员,而不是数组的indexOf方法!所以需要在使用indexOf的时候,需要Vue内部同时遍历所有数组成员,让所有数组成员也伴随收集依赖。
  2. 为什么设置对象的值的时候要使用Reflect?

    1. Reflect具有健壮性,发生错误会返回false,而不是报错。可以参考这里
    2. 最重要的是Reflect可以保持继承proxy为原型时,且proxy的target有getter时,这个getter的this指向。可以参考这里
  3. 当返回值是对象或者数组时,延时创建响应式

干货说Vue3响应式实现

四. get track

track的实现,需要着重了解 两个变量,一个概念,一个过程。

  1. 两个变量

    1. activeEffect:当前全局唯一激活的副作用函数
    2. targetMap:依赖收集表,是一个weakMap, 因为原始变量一旦被删除,和它相关的所有副作用函数就都没用了,会被垃圾清除,用Map,则需要手动清理。
  2. 一个概念:

    1. effect副作用函数:effect是一个高阶函数,解决了函数在执行时,内部依赖的响应式变量不知道自己在哪个函数里被访问的问题。所有想要收集响应式依赖的函数,都需要被effect函数包裹。
  3. 一个过程:

    1. 在targetMap中为target创建Map,然后为key创建depSet,将当前全局唯一的activeEffect作为依赖add进入depSet中,之后将depSet作为target的值。形成一个target[key]的依赖收集表:

干货说Vue3响应式实现

track执行流程图:

干货说Vue3响应式实现

五. 什么是effect副作用函数

你需要知道:

  1. 为什么需要副作用函数?
    1. 这个辅佐用函数其实就是高阶函数,因为在函数执行时候,如果没有外面包一层的高阶函数,依赖收集的过程就不知道当前所在执行环境是什么。
  2. 两个全局变量:activeEffect、effectStack
  3. effect的执行过程: 先清除effect引用、将effect推入effectStack、activeEffect指向当前effect,执行原函数。
  4. 为什么要在入栈前先清除effect引用:
    1. 因为最新的副作用函数,比如 render effect,最新的模板已经不再依赖先前依赖的某个响应式变量。当这个响应式变量发生改变时,render effect此时就不再需要执行了。

你还需要知道:

  1. reactiveEffect函数是有options配置的,在effect执行时带入,createReactiveEffect时候赋值给reactiveEffect.options

    1. lazy:默认true,也就是在执行effect的时候,只会创建并返回reactiveEffect,不会执行reactiveEffect。为false时,不仅返回,还会当即执行reactiveEffect。

      TIP:在创建computed Effect的时,lazy为true,表示需要延迟执行。

    2. scheduler:调度器,是一个函数,默认没有scheduler,主要在trigger后,通过run触发reactiveEffect执行时,如果有reactiveEffect.option.schedule存在,就会执行它的scheduler,否则就会执行reactiveEffect本身

effect流程图

干货说Vue3响应式实现

六. Set trigger

重点需要知道:

  1. 从 depset 中取出所有 effect,并用执行器 run 遍历执行的过程。
  2. effect函数有调度scheduler的概念,调度器 run 会先判断是否有调度

干货说Vue3响应式实现

七. Ref Api

Ref api 做的事情非常简单,重点知道它解决的两个问题:

  1. 因为基本类型不能通过proxy直接监听,所以需要给基本类型包一层对象,然后通过给这个对象value属性设置getter 和 setter的方式去监听。getter就是去track,set就是去trigger。
  2. 如果是对象或者数组或者是WeakMap等...响应式白名单中的数据类型,那么value就会直接转换成reactive的返回结果。

下面是Ref Api的大体实现:

function ref(value) {
  return createRef(value)
}

const convert = (val) => isObject(val) ? reactive(val) : val

function createRef(rawValue) {
  if (isRef(rawValue)) {
    // 如果传入的就是一个 ref,那么返回自身即可,处理嵌套 ref 的情况。
    return rawValue
  }
  // 如果是对象或者数组类型,则转换一个 reactive 对象。
  let value = convert(rawValue)
  const r = {
    __v_isRef: true,
    get value() {
      // getter
      // 依赖收集,key 为固定的 value
      track(r, "get" /* GET */, 'value')
      return value
    },
    set value(newVal) {
      // setter,只处理 value 属性的修改
      if (hasChanged(toRaw(newVal), rawValue)) {
        // 判断有变化后更新值
        rawValue = newVal
        value = convert(newVal)
        // 派发通知
        trigger(r, "set" /* SET */, 'value', void 0)
      }
    }
  }
  return r
}
转载自:https://juejin.cn/post/7372031054818410515
评论
请登录