Vue3源码学习-响应式原理-reactive

作用

reactive和 ref一样都是用于声明响应式状态的方式

局限性

reactive只能用于对象类型(对象、数组和像Map、Set这样的集合类型),不可以对基本数据类型进行响应式处理

使用

这里摘取官网的案例

js 复制代码
// 在模版中
<button @click="state.count++"> {{ state.count }} </button>

// js
import { reactive } from 'vue'
const state = reactive({ count: 0 })

源码解析

让我们看看const state = reactive({ count: 0 })这一行代码发生了什么?

js 复制代码
export function reactive(target: object) {
  // * 如果传入的target是只读,则直接返回
  if (isReadonly(target)) {
    return target
  }
  // * 返回一个代理对象
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap,
  )
}
  • 这里我们可以看出执行reactive函数
    • 会先检查传入的值是否是只读,是的话则直接返回target
    • target非只读,则会返回createReactiveObject函数的返回值,并传入四个参数

接下来我们解析下createReactiveObject函数内部

js 复制代码
function createReactiveObject(
  target: Target,
  isReadonly: boolean, // * 是否是只读的,初次调用时为false
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>,
) {
  // * 如果target不是一个对象则直接返回
  if (!isObject(target)) {
    if (__DEV__) {
      // * 如果处于开发环境则打印警告
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // * target[ReactiveFlags.RAW] 为true说明target已经被包装过了,是一个响应式对象的原始版本
  // * (isReadonly && target[ReactiveFlags.IS_REACTIVE]) 是否是只读的响应式对象
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // * 在proxyMap中查找是否已经有target的代理对象
  const existingProxy = proxyMap.get(target)
  // * 如果已经有了则直接返回
  if (existingProxy) {
    return existingProxy
  }
  // * 判断target是否是一个可以被监听的对象
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    // * 如果target是一个不可以监听的对象则返回
    return target
  }
  // * 创建一个代理对象,TargetType.COLLECTION表示target是一个集合对象
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
    // * TargetType.COLLECTION 为true则代表是一个集合类型的对象
  )
  // * 将target和proxy存入proxyMap中
  proxyMap.set(target, proxy)
  // * 返回代理对象
  return proxy
}
  • createReactiveObject接受四个参数

    • target:需要进行代理的对象也就是{count: 0}
    • isReadonly:是否只读
    • baseHandlers:非集合类型的handler
    • collectionHandlers:集合类型使用的handler
    • proxyMap:存储target的代理对象
  • 其本质就是查询是否存在target的代理对象,如果有则返回,没有,则重新创建一个代理对象,并将新创建的代理对象存入reactiveMap中

接下来解析下返回出去的代理对象proxy

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

js 复制代码
class BaseReactiveHandler implements ProxyHandler<Target> {
  constructor(
    protected readonly _isReadonly = false,
    protected readonly _shallow = false,
  ) {}

  get(target: Target, key: string | symbol, receiver: object) {
    const isReadonly = this._isReadonly,
      shallow = this._shallow
     // * 传入的值是非响应式,下面的判断不会被触发
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return shallow
    } else if (key === ReactiveFlags.RAW) {
      if (
        receiver ===
          (isReadonly
            ? shallow
              ? shallowReadonlyMap
              : readonlyMap
            : shallow
              ? shallowReactiveMap
              : reactiveMap
          ).get(target) ||
        // receiver is not the reactive proxy, but has the same prototype
        // this means the reciever is a user proxy of the reactive proxy
        Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
      ) {
        return target
      }
      // early return undefined
      return
    }

    // * targetIsArray: target是否是数组  
    const targetIsArray = isArray(target)

    // * 是否只读
    if (!isReadonly) {
      // * 如果是数组,且key是数组的索引
      if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
        return Reflect.get(arrayInstrumentations, key, receiver)
      }
      if (key === 'hasOwnProperty') {
        return hasOwnProperty
      }
    }

    // * Reflect.get(target, key, receiver) 会触发getter,返回target[key]
    const res = Reflect.get(target, key, receiver)

    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    // * 依赖收集
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    if (shallow) {
      return res
    }

    if (isRef(res)) {
      // ref unwrapping - skip unwrap for Array + integer key.
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }


    if (isObject(res)) {
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      // * 如果返回值是对象,且不是只读的,递归包装成响应式对象
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

// * 1.继承了BaseReactiveHandler实现get方法
class MutableReactiveHandler extends BaseReactiveHandler {
  constructor(shallow = false) {
    super(false, shallow)
  }

  set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object,
  ): boolean {
    // * 存储旧值
    let oldValue = (target as any)[key]
    if (!this._shallow) {
      const isOldValueReadonly = isReadonly(oldValue)
      if (!isShallow(value) && !isReadonly(value)) {
        // * 旧值和新值都转换成原始值
        oldValue = toRaw(oldValue)
        value = toRaw(value)
      }
      // * 如果不是数组、旧值是ref类型、新值不是ref类型
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        if (isOldValueReadonly) {
          return false
        } else {
          oldValue.value = value
          return true
        }
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }

    // 查看是否有key
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // * target是否为原始值,也就是没有包装的值
    if (target === toRaw(receiver)) {
      // * 如果没有key
      if (!hadKey) {
        // * 触发添加操作
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        // * 触发修改操作
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
  • 当我们查看state.count的值时发生了什么?
    *
    • state.count执行了代理对象的get方法,也就是BaseReactiveHandler中的get
    • const res = Reflect.get(target, key, receiver)最关键的是这一行代码,返回了state[count]也就是0
  • 为什么reactive()传入多层对象,对响应式对象所有属性的访问和修改,都可以被依赖追踪到,就在第80行代码
js 复制代码
if (isObject(res)) {
      // * 如果返回值是对象,且不是只读的,递归包装成响应式对象
      return isReadonly ? readonly(res) : reactive(res)
   }
  • 当我们修改state.count的值发生了什么?
    • 核心就是这一行const result = Reflect.set(target, key, value, receiver);

总结

  • reactive响应式是如何做到的?

    • 当我们调用reactive时,会先检查传入的值是不是一个只读对象,如果不是则会调用createReactiveObject函数
    • 在createReactiveObject函数中会判断传入的target是不是一个对象,如果不是会直接返回
    • 在reactiveMap查找是否存在传入target的代理对象,如果存在会直接返回代理对象,不存在则会重新创建,并将新创建的proxy存入reactiveMap
  • 当我们在reactive函数中传入多层嵌套对象,它是如何做到响应式的?

    • 在BaseReactiveHandler中,get方法会判断查看的值是否也是一个对象并且不是只读的,将会递归包装成响应式对象
相关推荐
卸任1 天前
Vibe coding工具使用心得
前端·ai编程
brien91 天前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance1 天前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M1 天前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写1 天前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李1 天前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易1 天前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马1 天前
前沿视界
前端
kyriewen1 天前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易1 天前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员