读读 Vue3 中 reactive 响应式源码

在 Vue3 里面是通过 ref() reactive() 这两个 api 来创建响应式数据的。

官方文档上写着 ref() 这个方法允许我们创建可以使用任何值类型的响应式ref, ref() 会把传入的参数包装成一个带 .value 属性的对象。

  • 一般来说 ref() 可以用来将 StringNumberBooleanNull 等原始类型定义成一个响应式对象。

  • reactive() 则用来将复杂类型数据定义成一个响应式对象,如 对象Map 等。

reactive

官方对于 reactive 函数相关的代码放在了core/packages/reactivity/src/reactive.ts这个文件夹里面,点击此处跳转至源码

在这个文件夹里面有四个响应式的核心方法:

  1. reactive(): 会将对象进行深层代理
  2. shallowReactive(): 浅层拷贝,只会对对象的第一层进行代理
  3. readonly(): 接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。
  4. shallowReadonly: 除了第一层只可读,后面的就可以访问改变,也就是浅层只读。

以下是我在源码找到的部分方法,当然跟源码有点不一样哈。单看可以分为两类:第一类是只读类(readonly);第二类是深层类(reactive)。这几个方法都是依靠 createReactiveObject() 这个核心方法将 target 变成响应式对象的。

 export function reactive(target){    
    return createReactiveObject(target,false,mutableHandlers)
 }
 export function shallowReactive(target){
    return createReactiveObject(target,false,shallowReactiveHandlers)
 }
 export function readonly(target){
    return createReactiveObject(target,true,readonlyHandlers)
 }
 export function shallowReadonly(target){
    return createReactiveObject(target,true,shallowReadonlyHandlers)
 }

再来细看源码中关于 reactive 函数的定义:

export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) {   // 如果传入的对象是只读的,那么直接返回该个只读对象
    return target
  }
  return createReactiveObject(   // 实现代理的核心
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

源码中 reactive 做的事情就是判断传入的对象是否为只读,如果是只读的对象就不进行代理。然后就是调用 createReactiveObject() 这个方法实现将传入的对象转换成响应式对象,这个方法也就是实现响应式代理的核心。

现在来看看实现响应式代理的核心方法:createReactiveObject()

function createReactiveObject(
  target: Target,    // 传入的目标
  isReadonly: boolean,  // 是否只读
  baseHandlers: ProxyHandler<any>,  // 实现代理的核心
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>  // 存储 target 对象的
) {
  if (!isObject(target)) {   // 先判断 reactive 代理的是不是一个对象,因为proxy只能代理一个对象
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  
  // 优化:如果目标数据已经被代理了,直接返回
  if (   
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  
  // proxyMap 中已经存入过 target,直接返回
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  
  // 只有特定类型的值才能被 observe.
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  
  // 如果上面的条件都不满足就通过 proxy 来代理一个响应式对象
  const proxy = new Proxy(   
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)   
  return proxy
}

创建reactive对象的函数的一个大概流程就是先判断目标是否为一个对象,否则 Proxy 就不能进行代理;然后判断目标是否已经被代理过,即:

target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE])

如果目标值的 FlagsRAW(我觉得这个标签就是给对象的响应式标签),也就是该 target 不是响应式,并且 target 也不是已经代理过的,否则就直接返回。

然后就是判断 proxyMap 中是否已经存入过 target

 const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }

再然后就是判断特定类型 getTargetType():

function getTargetType(value: Target) {
  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    ? TargetType.INVALID
    : targetTypeMap(toRawType(value))
}

这个函数看着有点懵…

然后不满足上面的条件就会创建一个 Proxy 对象,对 target 进行代理。

 const proxy = new Proxy(   
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )

Proxy 是根据 targetType 来确定执行的是 collectionHandlers代理处理程序 还是 baseHandlers基础处理程序

collectionHandlers是将集合、数组转换成代理对象的一些程序。baseHandlers 则是将对象转换成代理对象的一些程序。

简单看看collectionHandlers,代码在 core/packages/reactivity/src/collectionHandlers.ts 目录

type IterableCollections = Map<any, any> | Set<any>
type WeakCollections = WeakMap<any, any> | WeakSet<any>
type MapTypes = Map<any, any> | WeakMap<any, any>
type SetTypes = Set<any> | WeakSet<any>

function get(){}
function has(){}
function size(){}
...

不难看出这是对于一些集合、数组进行处理的函数。

对于 baseHandlers ,代码在 core/packages/reactivity/src/baseHandlers.ts 目录,当然官方有更详细的介绍 BaseHandlers

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

创建完 Proxy 对象之后就是将对象存放到proxymap中,再将 proxy 对象返回出来。然后针对四个方法,通过在createReactiveObject 里面传入不同的参数,封装出 reactiveshallowReactivereadonlyshallowReadonly样式的对象。实现对象的一个响应式代理。

本文正在参加「金石计划」

原文链接:https://juejin.cn/post/7217795738691977253 作者:布鲁斯要蓝调

(0)
上一篇 2023年4月4日 下午4:10
下一篇 2023年4月4日 下午4:20

相关推荐

发表评论

登录后才能评论