【vue3学习系列】个人对reactive源码的初略分析

前言

如果源码解释不对,麻烦过客留下宝贵的留言,感激不尽。

源码文件入口vue-next\packages\reactivity\src\reactive.ts

文章版本v1.0(后续会文章优化迭代)

第一次执行

源码分析

平时使用reactive时,调用的就是reactive.ts文件里的reactive()函数:

setup(props) {
	const obj = reactive({a: 1});
}

来看看这个reactive函数源码:

// 先看看reactive函数会用到的东西
export const enum ReactiveFlags { // 这里定义了几个枚举值,用于下面Target接口的key
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly', 
  RAW = '__v_raw'
}

export interface Target {
  [ReactiveFlags.SKIP]?: boolean
  [ReactiveFlags.IS_REACTIVE]?: boolean
  [ReactiveFlags.IS_READONLY]?: boolean
  [ReactiveFlags.RAW]?: any
}
// Target相当于
export interface Target {
  '__v_skip'?: boolean
  '__v_isReactive'?: boolean // 是否已经被处理成响应式
  '__v_isReadonly'?: boolean // 是否只读
  '__v_raw'?: any // 是否是原生对象(未被处理过的)
}

了解后开始看reactive:

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T> // T extends object 泛型约束为对象,也就是传入的变量编辑器要认定为对象,返回的UnwrapNestedRefs<T> 先不解释
// (也不知道这块函数的定义是不是下个函数的重载写法)

// 开始进入reactive函数
export function reactive(target: object) {
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) { // 如果有值传进函数,并且是只读的那就直接返回
    return target
  }
  // 返回一个创建响应式对象的函数,接下来看看这个函数是什么
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

createReactiveObject

function createReactiveObject(
  target: Target, // 传进来的变量
  isReadonly: boolean, // 是否只读
  baseHandlers: ProxyHandler<any>, // 后面再分析
  collectionHandlers: ProxyHandler<any>, // 后面再分析
  proxyMap: WeakMap<Target, any> // 后面再分析
) {
  if (!isObject(target)) { // 判断是否为一个引用类型,不是就返回(__DEV__为true的话就再多出个报错信息)
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  if (
    target[ReactiveFlags.RAW] && // 如果是没经过响应式处理过的,而且不是一个只读响应式对象就继续走下去
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  const existingProxy = proxyMap.get(target) // proxyMap存放了已经被处理的对象集,所以这里看看是不是之前已经响应式处理过放在这里了
  if (existingProxy) {
    return existingProxy // 存过就直接返回
  }
  // only a whitelist of value types can be observed.
  const targetType = getTargetType(target) // 鉴别target的类型(做了类型的归类),处理过程就略过了,只要知道的是返回0就是无效的,不可拓展的对象,是'Object'和'Array'类型,2是'Map'、'Set'、'WeakMap'、'WeakSet'类型
  if (targetType === TargetType.INVALID) { // 如果为0,直接返回
    return target
  }
  const proxy = new Proxy( // 通过Proxy将target进行响应式处理
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers // 后面再分析collectionHandlers与baseHandlers
  )
  proxyMap.set(target, proxy) // 并将处理结果存在proxyMap里
  return proxy // 返回处理结果
}

实践分析

当我们第一次执行reactive函数时,传进来的对象并没有经过响应式处理,也没有ReactiveFlags上枚举的哪些属性,所以进入这个函数基本都是一路下来到new Proxy的执行,然后把结果保存在proxyMap中后返回处理结果。

可以自己亲自在const obj = reactive({a: 1});上打个断点,一步一步的调试去查看源码是怎么走的。


createReactiveObject函数的入参

提示:在接着讲之前,需要知道proxy的使用方法。

前面提到在reactive调用createReactiveObject函数的时候,还有三个参数没讲到mutableHandlers、mutableCollectionHandlers、reactiveMap。

在createReactiveObject函数中的new Proxy部分就用到了mutableHandlers、mutableCollectionHandlers。

function createReactiveObject(
  target: Target, // 传进来的变量
  isReadonly: boolean, // 是否只读
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>, 
  proxyMap: WeakMap<Target, any> 
) {
	//....
	 const proxy = new Proxy( // 通过Proxy将target进行响应式处理
	    target,
	    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers // 如果targetType是2('Map'、'Set'、'WeakMap'、'WeakSet'类型),那么就以collectionHandlers作为proxy的第二参数,否则'Object'和'Array'类型就以baseHandlers作为第二参数
	  )
 	//...
 }

因为我们常用的对象类型就是’Object’和’Array’类型,那么就看看baseHandlers,也就是reactive函数传入的mutableHandlers

mutableHandlers

源码:

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

这里我们就只看get、set和deleteProperty就行了:

get

get 方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。

const get = /*#__PURE__*/ createGetter() //  返回一个proxy的get方法

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    if (key === ReactiveFlags.IS_REACTIVE) { // 获取__v_isReactive上的值
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) { // 获取__v_isReadonly上的值
      return isReadonly
    } else if ( // 获取原生未被处理的对象
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target 
    }

    const targetIsArray = isArray(target) // 判断target是否是一个数组

    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) { // 对象是一个数组,并且key是符合vue改写后的数组api的(arrayInstrumentations应该是vue3把数组的几个api方法加工后的数组类型)
      return Reflect.get(arrayInstrumentations, key, receiver) // 那就返回一个用于数组的get方法替代现在进入的这个get方法
    }

    const res = Reflect.get(target, key, receiver) // 获取key的值

    // 接下来是对key的值做处理

    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { // 不需要跟踪的key
      return res
    }

    if (!isReadonly) { // 非只读的
      track(target, TrackOpTypes.GET, key) // 这个作用是,当值发生改变,通知用到的dom重新渲染
    }

    if (shallow) { // 是浅响应式的,直接返回
      return res
    }

    if (isRef(res)) { // 如果key是一个ref(说明支持key为一个ref处理过的对象)
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res // 如果不是一个数组或不是一个有下标作为key的变量,就返回.value
    }

    if (isObject(res)) { // key是一个对象,那么如果设置只读,就经过只读处理,没有就经过响应式处理(应该就是通过改写proxy的get实现遍历属性响应式处理)
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

set

再来看看set:

set 方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

const set = /*#__PURE__*/ createSetter() // 返回一个set方法

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key] // 拿到旧的值
    if (!shallow) { // 如果不是浅响应式对象
      // 进行原始化处理
      value = toRaw(value) 
      oldValue = toRaw(oldValue)
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) { // 当外界xxx.a = 1改写了值,且旧值是通过ref进行的响应式处理
        oldValue.value = value // 把.vulue更新一下
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }

    const hadKey = // 看看是不是新添加的key
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver) // 这里拿到的是个布尔值(不知道为什么createSetter方法要返回一个布尔值)
    // don't trigger if target is something up in the prototype chain of original
    if (target === toRaw(receiver)) { // 这种场景很少出现,翻译过来就是:如果目标在原始原型链中,则不触发
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value) // 新添加了key就跟踪到使用的地方
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue) // 修改了也跟踪
      }
    }
    return result
  }
}

deleteProperty

这就是删除

function deleteProperty(target: object, key: string | symbol): boolean {
  const hadKey = hasOwn(target, key) // 是否有这key
  const oldValue = (target as any)[key] // 拿到旧值
  const result = Reflect.deleteProperty(target, key)
  if (result && hadKey) {
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue) // 删除后把用到的地方全部调用一遍
  }
  return result 
}

动手实现

有篇文章已经总结的很好了Vue 3 深入响应式原理 - 聊一聊响应式构建的那些经历

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值