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

作用

reactiveref一样都是用于声明响应式状态的方式

局限性

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

使用

这里摘取官网的案例

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方法会判断查看的值是否也是一个对象并且不是只读的,将会递归包装成响应式对象
相关推荐
涔溪40 分钟前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
榴莲千丞1 小时前
第8章利用CSS制作导航菜单
前端·css
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与1 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun1 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇1 小时前
ES6进阶知识一
前端·ecmascript·es6
渗透测试老鸟-九青2 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss
龙猫蓝图2 小时前
vue el-date-picker 日期选择器禁用失效问题
前端·javascript·vue.js
fakaifa2 小时前
CRMEB Pro版v3.1源码全开源+PC端+Uniapp前端+搭建教程
前端·小程序·uni-app·php·源码下载
夜色呦2 小时前
掌握ECMAScript模块化:构建高效JavaScript应用
前端·javascript·ecmascript