Vue3 和 Vue2 的响应式有很大的不同,因为 Vue3 应用 Proxy 代替了 defineProperty,使得 Vue3 比 Vue2 在响应式数据处理方面有着更好的性能,更简洁高效的解决形式,还实现了诸多在 Vue2 上无奈实现的性能。此外 Vue3 的响应式库 reactivity 是一个独自的包,它能够不依赖 Vue 运行,意味着咱们能够将它运行在其余框架里。事实上,Vue3 的响应式库的实现形式以及市面上其余的大多数响应式库(如 observer-util,meteor 等)的实现形式都是相似的,Vue 也是参考这些库实现的,所以咱们还是很有必要去钻研一下的,毕竟咱也不能掉队了 ????,那么各位小伙伴们上面就跟我一起来看下这个 @vue/reactivity 到底是怎么实现的。

本文章的源码曾经发在了我的 git 上,能够返回查看:reactivity

浏览本文章之前你要先理解以下知识点

  • WeakMap
  • Set
  • Reflect
  • Proxy

下面这些有不理解的同学能够间接点链接查看具体的文档,文章外面就不再解释了。

--

咱们首先看一个应用 reactivity 的例子

// 创立一个响应式对象const state = reactive({ count: 1 })// 执行effecteffect(() => {  console.log(state.count)})state.count = 2 // count扭转时执行了effect内的函数,控制台输入2

这个例子通过 reactive 创立了一个响应式对象 state,而后调用 effect 执行函数,这个函数外部拜访了 state 的属性,随后咱们更改这个 state 的属性,这时,effect 内的函数会再次执行。

这样一个响应式数据的通常实现的形式是这样的

  1. 定义一个数据为响应式(通常通过 defineProperty 或者 Proxy 拦挡 get、set 等操作)
  2. 定义一个副作用函数(effect),这个副作用函数外部拜访到响应式数据时会触发 1 中的 getter,进而能够在这里将 effect 收集起来
  3. 批改响应式数据时,就会触发 1 中的 setter,进而执行 2 中收集到的 effect 函数
对于 effect: effect 在 Vue 里通常叫做副作用函数,因为这种函数内通常执行组件渲染,计算属性等其余工作。在其余库外面可能叫观察者函数(observe)或其余,集体能了解到是什么意思就好,因为本篇文章是剖析 Vue3 的,所以对立叫副作用函数(effect)

依据以上的思路,咱们就能够开始入手实现了

reactive

首先咱们须要有一个 reactive 函数来将咱们的数据变为响应式。

// reactive.tsimport { baseHandlers } from './handlers'import { isObject } from './utils'type Target = objectconst proxyMap = new WeakMap()export function reactive<T extends object>(target: T): T {  return createReactiveObject(target)}function createReactiveObject(target: Target) {  // 只对对象增加reactive  if (!isObject(target)) {    return target  }  // 不能反复定义响应式数据  if (proxyMap.has(target)) {    return proxyMap.get(target)  }  // 通过Proxy拦挡对数据的操作  const proxy = new Proxy(target, baseHandlers)  // 数据增加进ProxyMap中  proxyMap.set(target, proxy)  return proxy}

这里次要对数据做了简略的判断,要害是在const proxy = new Proxy(target, baseHandlers)中,通过 Proxy 对数据进行解决,这里的baseHandlers就是对数据的 get,set 等拦挡操作,上面来实现下baseHandlers

get 收集依赖

首先实现下拦挡 get 操作,使得拜访数据的某一个 key 时,能够收集到拜访这个 key 的函数(effect),并把这个函数储存起来。

// handlers.tsimport { track } from './effect'import { reactive, Target } from './reactive'import { isObject } from './utils'export const baseHandlers: ProxyHandler<object> = {  get(target: Target, key: string | symbol, receiver: object) {    // 收集effect函数    track(target, key)    // 获取返回值    const res = Reflect.get(target, key, receiver)    // 如果是对象,要再次执行reactive并返回    if (isObject(res)) {      return reactive(res)    }    return res  }}

这里咱们拦挡到 get 操作后,通过 track 收集依赖,track 函数做的事件就是把以后的 effect 函数收集起来,执行完 track 后,再获取到 target 的 key 的值并返回,留神这里是判断了下 res 是否是对象,如果是对象的话要返回reactive(res),是因为思考到可能有多个嵌套对象的状况,而 Proxy 只能批改到到以后对象,并不能批改到子对象,所以在这里要解决下,上面咱们须要再实现track函数

// effect.ts// 存储依赖type Deps = Set<ReactiveEffect>// 通过key去获取依赖,key => Depstype DepsMap = Map<any, Deps>// 通过target去获取DepsMap,target => DepsMapconst targetMap = new WeakMap<any, DepsMap>()// 以后正在执行的effectlet activeEffect: ReactiveEffect | undefined// 收集依赖export function track(target: object, key: unknown) {  if (!activeEffect) {    return  }  // 获取到这个target对应的depsMap  let depsMap = targetMap.get(target)  // depsMap不存在时新建一个  if (!depsMap) {    targetMap.set(target, (depsMap = new Map()))  }  // 有了depsMap后,再依据key去获取这个key所对应的deps  let deps = depsMap.get(key)  // 也是不存在时就新建一个  if (!deps) {    depsMap.set(key, (deps = new Set()))  }  // 将activeEffect增加进deps  if (!deps.has(activeEffect)) {    deps.add(activeEffect)  }}

留神有两个 map 和一个 set,targetMap => depsMap => deps,这样就能够使咱们通过 target 和 key 精确地获取到这个 key 所对应的 deps(effect),把以后正在执行的 effect(activeEffect)存起来,这样在批改target[key]的时候,就又能够通过 target 和 key 拿到之前收集到的所有的依赖,并执行它们,这里有个问题就是这个activeEffect它是从哪里来的,get 是怎么晓得以后正在执行的 effect 的?这个问题能够先放一放,咱们前面再将,上面咱们先实现这个 set。

实现 set

// handlers.tsexport const baseHandlers: ProxyHandler<object> = {  get() {    //...  },  set(target: Target, key: string | symbol, value: any, receiver: object) {    // 设置value    const result = Reflect.set(target, key, value, receiver)    // 告诉更新    trigger(target, key, value)    return result  }}

咱们在方才的baseHandlers上面再加一个 set,这个 set 外面次要就是赋值而后告诉更新,告诉更新通过trigger进行,咱们须要拿到在 get 中收集到的依赖,并执行,上面来实现下 trigger 函数

// effect.ts// 告诉更新export function trigger(target: object, key: any, newValue?: any) {  // 获取该对象的depsMap  const depsMap = targetMap.get(target)  // 获取不到时阐明没有触发过getter  if (!depsMap) {    return  }  // 而后依据key获取deps,也就是之前存的effect函数  const effects = depsMap.get(key)  // 执行所有的effect函数  if (effects) {    effects.forEach((effect) => {      effect()    })  }}

这个 trigger 就是获取到之前收集的 effect 而后执行。

其实除了 get 和 set,还有个罕用的操作,就是删除属性,当初咱们还不能拦挡到删除操作,上面咱们来实现下

实现 deleteProperty

export const baseHandlers: ProxyHandler<object> = {  get() {    //...  },  set() {    //...  },  deleteProperty(target: Target, key: string | symbol) {    // 判断要删除的key是否存在    const hadKey = hasOwn(target, key)    // 执行删除操作    const result = Reflect.deleteProperty(target, key)    // 只在存在key并且删除胜利时再告诉更新    if (hadKey && result) {      trigger(target, key, undefined)    }    return result  }}

咱们在方才的baseHandlers外面再加一个deleteProperty,它能够拦挡到对数据的删除操作,在这里咱们须要先判断下删除的 key 是否存在,因为可能用户会删除一个并不存在 key,而后执行删除,咱们只在存在 key 并且删除胜利时再告诉更新,因为如果 key 不存在时,这个删除是无意义的,也就不须要更新,再有就是如果删除操作失败的话,也不须要更新,最初间接触发trigger就能够了,留神这里的第三个参数即 value 是undefined

当初咱们曾经实现了getsetdeleteProperty这三种操作的拦挡,还记不记得在track函数中的activeEffect,那里留了个问题,就是这个activeEffect是怎么来的?,在最开始的例子外面,咱们要通过 effect 执行函数,这个activeEffect就是在这里设置的,上面咱们来实现下这个effect函数。

// effect.tstype ReactiveEffect<T = any> = () => T// 存储effect的调用栈const effectStack: ReactiveEffect[] = []export function effect<T = any>(fn: () => T): ReactiveEffect<T> {  // 创立一个effect函数  const effect = createReactiveEffect(fn)  return effect}function createReactiveEffect<T = any>(fn: () => T): ReactiveEffect<T> {  const effect = function reactiveEffect() {    // 以后effectStack调用栈不存在这个effect时再执行,防止死循环    if (!effectStack.includes(effect)) {      try {        // 把以后的effectStack增加进effectStack        effectStack.push(effect)        // 设置以后的effect,这样Proxy中的getter就能够拜访到了        activeEffect = effect        // 执行函数        return fn()      } finally {        // 执行完后就将以后这个effect出栈        effectStack.pop()        // 把activeEffect复原        activeEffect = effectStack[effectStack.length - 1]      }    }  } as ReactiveEffect<T>  return effect}

这里次要是通过createReactiveEffect创立一个 effect 函数,fn 就是调用 effect 时传入的函数,在执行这个 fn 之前,先通过effectStack.push(effect)把这个 effect 推入 effectStack 栈中,因为 effect 可能存在嵌套调用的状况,保留下来就能够获取到一个残缺的 effect 调用栈,就能够通过下面的effectStack.includes(effect)判断是否存在循环调用的状况了,而后再activeEffect = effect设置 activeEffect,设置完之后再执行 fn,因为这个 activeEffect 是全局惟一的,所以咱们执行 fn 的时候,如果外部拜访了响应式数据,就能够在 getter 里拿到这个 activeEffect,进而收集它。

当初基本上是实现了,当初通过咱们写的这个 reactivity 库就能够实现例子中的成果了,然而还有一些边界状况须要思考,下篇文章就增加一些常见的边界状况解决。