Vue3 源码分析reactive readonly实例

目录
  • 引言
  • 一、reactive 和 readonly
    • 1. reactive相关类型
    • 2. 相关全局变量与方法
    • 3. reactive函数
    • 4. 造物主createReactiveObject
    • 5. shallowReactive、readonly和shallowReadonly
  • 二、对应的 Handlers
    • 1. baseHandlers
      • 1.1 reactive
      • 1.2 readonly
      • 1.3 shallowReactive
      • 1.4 shallowReadonly
    • 2. cellectionHandlers
      • 1.1 createInstrumentationGetter
      • 1.2 instrumentations
      • 1.3 createIterableMethod
  • 小结

引言

上次一起阅读了watch和computed的源码,其实应该先看副作用effect,因为各个响应式的API里基本都用到了,等结束了reactive和readonly和ref,就一起看看effect。这次要说的是reactive和readonly,两者在实现上流程大体一致。尤其是对Map和Set的方法的代理拦截,多少有点妙。

一、reactive 和 readonly

Vue3使用Proxy来替代Vue2中Object.defineProperty。

const target = {
  name: 'onlyy~'
}
// 创建一个对target的代理
const proxy = new Proxy(target, {
  // ...各种handler,例如get,set...
  get(target, property, receiver){
    // 其它操作
    // ...
    return Reflect.get(target, property, receiver)
  }
})

1. reactive相关类型

reactive利用Proxy来定义一个响应式对象。

  • Target:目标对象,包含几个标志,以及__v_raw字段,该字段表示它原本的非响应式状态的值;
export interface Target {
  [ReactiveFlags.SKIP]?: boolean
  [ReactiveFlags.IS_REACTIVE]?: boolean
  [ReactiveFlags.IS_READONLY]?: boolean
  [ReactiveFlags.IS_SHALLOW]?: boolean
  [ReactiveFlags.RAW]?: any
}
export const reactiveMap = new WeakMap<Target, any>()
export const shallowReactiveMap = new WeakMap<Target, any>()
export const readonlyMap = new WeakMap<Target, any>()
export const shallowReadonlyMap = new WeakMap<Target, any>()
const enum TargetType {
  INVALID = 0,
  COMMON = 1,
  COLLECTION = 2
}

2. 相关全局变量与方法

  • ReactiveFlags:定义了各种标志对应的字符串(作为reactive对象的属性)的枚举;
  • reactiveMap
  • shallowReactiveMap
  • readonlyMap
  • shallowReadonlyMap:这几个Map分别用于存放对应API生成的响应式对象(以目标对象为key,代理对象为value),便于后续判断某个对象是否存在已创建的响应式对象;
  • TargetType:枚举成员的内容分别用于区分代理目标是否校验合法、普通对象、Set或Map;
// 各个标志枚举
export const enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  IS_SHALLOW = '__v_isShallow',
  RAW = '__v_raw'
}
// ...
export const reactiveMap = new WeakMap<Target, any>()
export const shallowReactiveMap = new WeakMap<Target, any>()
export const readonlyMap = new WeakMap<Target, any>()
export const shallowReadonlyMap = new WeakMap<Target, any>()
const enum TargetType {
  INVALID = 0,
  COMMON = 1,
  COLLECTION = 2
}

然后是两个函数:targetTypeMap用于判断各种JS类型属于TargetType中的哪种;getTargetType用于获取target对应的TargetType类型。

function targetTypeMap(rawType: string) {
  switch (rawType) {
    case 'Object':
    case 'Array':
      return TargetType.COMMON
    case 'Map':
    case 'Set':
    case 'WeakMap':
    case 'WeakSet':
      return TargetType.COLLECTION
    default:
      return TargetType.INVALID
  }
}
function getTargetType(value: Target) {
  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    ? TargetType.INVALID
    : targetTypeMap(toRawType(value))
}

3. reactive函数

reactive入参类型为object,返回值类型是UnwrapNestedRefs,对嵌套的Ref进行了解包。意味着即使reactive接收一个Ref,其返回值也不用再像Ref那样通过.value来读取值。源码的注释中也给出了示例。

/*
 * const count = ref(0)
 * const obj = reactive({
 *   count
 * })
 *
 * obj.count++
 * obj.count // -> 1
 * count.value // -> 1
 */

reactive内部调用createReactiveObject来创建响应式对象。瞄一眼入参有五个:

  • target:代理目标;
  • false:对应createReactiveObject的isReadonly参数;
  • mutableHandlers:普通对象和数组的代理处理程序;
  • mutableCollectionHandlers:Set和Map的代理处理程序;
  • reactiveMap:之前定义的全局变量,收集reactive对应的依赖。
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
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
  )
}

4. 造物主createReactiveObject

不论是reactive,还是shallowReactive、readonly和shallowReadonly,都是内部调用createReactiveObject来创建代理的。createReactiveObject也没什么操作,主要判断了下target的类型,再决定是直接返回target还是返回一个新建的proxy。

以下情况直接返回target:

  • target不是对象;
  • target已经是一个响应式的对象,即由createReactiveObject创建的proxy;
  • target类型校验不合法,例如RegExp、Date等;

当参数proxyMap对应的实参(可能为reactiveMap、shallowReactiveMap、readonlyMap或shallowReadonlyMap,分别对应ractive、shallowReactive、readonly和shallowReadonly四个API)里已经存在了target的响应式对象时,直接取出并返回该响应式对象;

否则,创建一个target的响应式对象proxy,将proxy加入到proxyMap中,然后返回该proxy。

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  if (!isObject(target)) {
    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
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

我们知道,代理的重点其实在与代理的处理程序,createReactiveObject根据普通对象和数组类型、Set和Map类型来区分baseHandlers和collectionHandlers。

5. shallowReactive、readonly和shallowReadonly

事实上,ractive、shallowReactive、readonly和shallowReadonly这几个函数形式上基本一致,都是通过createReactiveObject来创建响应式对象,存储在对应的proxyMap里,但是对应的baseHandlers和collectionHandlers有区别。

// shallowReactive
export function shallowReactive<T extends object>(
  target: T
): ShallowReactive<T> {
  return createReactiveObject(
    target,
    false,
    shallowReactiveHandlers,
    shallowCollectionHandlers,
    shallowReactiveMap
  )
}
// raedonly
// 注意readonly不是响应式的,而是一个原对象的只读的拷贝
// 具体实现在对应的handlers里
export function readonly<T extends object>(
  target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
  return createReactiveObject(
    target,
    true,
    readonlyHandlers,
    readonlyCollectionHandlers,
    readonlyMap
  )
}
// shallowReadonly
// 是响应式的
// 只有最外层是只读的
export function shallowReadonly<T extends object>(target: T): Readonly<T> {
  return createReactiveObject(
    target,
    true,
    shallowReadonlyHandlers,
    shallowReadonlyCollectionHandlers,
    shallowReadonlyMap
  )
}

事实上,ractive、shallowReactive、readonly和shallowReadonly这几个函数形式上基本一致,都是通过createReactiveObject来创建响应式对象,存储在对应的proxyMap里,但是对应的baseHandlers和collectionHandlers有区别。那么我们就知道了,其实重点都在各种handlers里。

二、对应的 Handlers

baseHandlers用于普通对象和数组的代理,collectionHandlers用于Set、Map等的代理。对应ractive、shallowReactive、readonly和shallowReadonly四个API,每一个都有自己的baseHandlers和collectionHandlers。

1. baseHandlers

在packages/reactivity/src/baseHandlers.ts文件中。分别导出了这4个API对应的baseHandlers。

1.1 reactive

reactive的baseHandlers中有5个代理程序。

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

在拦截过程中,在get、has和ownKey这几个访问程序中进行依赖捕获(track),在set和deleteProperty这俩用于更改的程序中触发更新(trigger) 。

get和set分别由函数createGetter和createSetter创建,这俩函数根据入参的不同,返回不同的get和set,readonly等API的baseHandlers中的get和set也大都源于此,除了两种readonly中用于告警的set。

(1) get

createGetter两个入参:isReadonly和isShallow,两两组合正好对应四个API。

  • shallow:为true时不会进入递归环节,因此是浅层的处理;
  • isReadonly:在createGetter中影响proxyMap的选择和递归时API的选择,它主要发挥作用是在set中。
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // 以下几个if分支判断target是否已经是由这几个API创建的代理对象,代理得到的proxy才具有这些key
    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 (
      // __v_raw 属性对应 代理对象的目标对象
      // 当该属性有值,且在相应的proxyMap中存在代理对象时,说明target已经是一个proxy了
      // __v_raw 属性对应的值为target本身
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }
    const targetIsArray = isArray(target)
    // 对数组的几个方法进行代理,在'includes', 'indexOf', 'lastIndexOf'等方法中进行track捕获依赖
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    const res = Reflect.get(target, key, receiver)
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }
    // 如果不是readonly,则捕获依赖,因此,readonly 为非响应式的
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
    if (shallow) {
      return res
    }
    // 如果get到的值是一个Ref,会直接解包,无需再使用 .value 来获取真正需要的值
    // 除非目标对象target是数组,或者当前的key是整数
    // 例如,obj[0],即使是一个Ref也不会直接解包,使用的时候依然要 obj[0].value
    // shallow没有走到这一步,因此也不会自动解包
    if (isRef(res)) {
      // ref unwrapping - skip unwrap for Array + integer key.
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }
    // 当get到的值是对象时,根据是否是readonly来递归操作,需要防止对象循环引用
    // shallow没有走到这一步,因此shallow是浅层的
    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
  }
}

(2) set

对于reactive,可以说最主要的任务就是在set中触发更新,set包括 新增 和 修改 属性值。如果当前的key对应的值是一个Ref,且其它条件满足时,则触发更新的操作是在Ref的内部。这些在后续讲解Ref的时候会提到。

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key]
    // 当前值是Readonly的Ref,而新值不是Ref时,不允许修改
    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
      return false
    }
    // 如果是深层的修改
    if (!shallow) {
      // 解出原本的非proxy值
      if (!isShallow(value) && !isReadonly(value)) {
        oldValue = toRaw(oldValue)
        value = toRaw(value)
      }
      // 目标对象非数组,当前key的值是Ref而新值不是Ref,则通过 .value 赋值
      // 在Ref内部触发更新
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    } else {
      // 浅层模式下,忽略对象是否是响应式的
      // in shallow mode, objects are set as-is regardless of reactive or not
    }
    // 然后是触发更新的部分了
    // 判断当前key是否已经存在于target上
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    // 如果是原型链上的字段则不会触发更新
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        // 当前的key已经存在,触发新增的更新
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        // 当前key不存在,触发修改的更新
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

(3) deleteProperty

删除操作的代理程序,和set一样,deleteProperty拦截delete和Reflect.deleteProperty()操作,它也能触发更新。

function deleteProperty(target: object, key: string | symbol): boolean {
  const hadKey = hasOwn(target, key)
  const oldValue = (target as any)[key]
  const result = Reflect.deleteProperty(target, key)
  // 删除成功 且 target中原来有这个属性时,触发删除的更新
  if (result && hadKey) {
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  return result
}

(4) has

has用于判断target中是否有当前的key,拦截a in obj、with(obj){(a)}、Reflect.has等操作,属于访问程序,在其中进行has操作的依赖收集。

function has(target: object, key: string | symbol): boolean {
  const result = Reflect.has(target, key)
  if (!isSymbol(key) || !builtInSymbols.has(key)) {
    track(target, TrackOpTypes.HAS, key)
  }
  return result
}

(5) ownKeys

用于获取target所有自身拥有的key,拦截Object.getOwnPropertyNames、Object.getOwnPropertySymbols、Object.keys、Reflect.ownKeys,属于访问程序,在其中进行迭代的依赖收集。

function ownKeys(target: object): (string | symbol)[] {
  track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
  return Reflect.ownKeys(target)
}

现在我们算是都弄明白了,对于普通对象和数组,reactive创建proxy,通过get、set、deleteProperty、has、ownKeys五个代理处理程序,来拦截其属性访问操作,在其中进行依赖收集,拦截其增删改操作,其中触发更新。

1.2 readonly

readonly的代理处理程序只有三个:

  • get:由createGetter(true)创建,还记得我们上面讲到的createSetter吗?
  • set
  • deleteProperty:这两个代理处理程序用于告警,毕竟readonly不可修改。

毕加思索一下createGetter(true),传入的readonly=true,使得get中不会进行track操作来收集依赖,因而不具有响应性。

const readonlyGet = /*#__PURE__*/ createGetter(true)
export const readonlyHandlers: ProxyHandler<object> = {
  get: readonlyGet,
  set(target, key) {
    if (__DEV__) {
      warn(
        `Set operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  },
  deleteProperty(target, key) {
    if (__DEV__) {
      warn(
        `Delete operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  }
}

1.3 shallowReactive

shallowReactive移植了reactive的baseHandlers,并且更新了get和set。具体实现也可以回顾上面说到的createGetter和createSetter。

回过头来看看createGetter(false, true),isReadonly = false,则在get中,可以进行track依赖收集;shallow = true,则在get中不会对顶层的Ref进行解包,也不会进行递归操作。

而在createSetter(true)中,参数shallow几乎只影响是否要解出原本的raw值。如果新值value不是浅层且不是只读的,则需要解出它的原本raw值,之后才能进行赋值操作,否则我们的shallowRef将不再是浅层的了。

const shallowGet = /*#__PURE__*/ createGetter(false, true)
const shallowSet = /*#__PURE__*/ createSetter(true)
export const shallowReactiveHandlers = /*#__PURE__*/ extend(
  {},
  mutableHandlers,
  {
    get: shallowGet,
    set: shallowSet
  }
)

1.4 shallowReadonly

移植了readonly的baseHandlers,更新了其中的get,这个get也试试由createGetter创建。我们知道,readonly的baseHandlers里,除了get,另外俩都是用来拦截修改操作并告警的。

回顾一下createGetter,当isReadonly===true时,不会进行track操作来收集依赖;shallow===true时,不会对Ref进行解包,也不会走到递归环节,即是浅层的readonly。

const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
// Props handlers are special in the sense that it should not unwrap top-level
// refs (in order to allow refs to be explicitly passed down), but should
// retain the reactivity of the normal readonly object.
export const shallowReadonlyHandlers = /*#__PURE__*/ extend(
  {},
  readonlyHandlers,
  {
    get: shallowReadonlyGet
  }
)

2. cellectionHandlers

对于Set和Map较为复杂的数据结构,他们有自己的方法,因此代理程序会有些差别。基本都是拦截它们原本的方法,然后进行track或trigger。可以看到这几个handlers中,都只有由createInstrumentationGetter创建的get。

export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: /*#__PURE__*/ createInstrumentationGetter(false, false)
}
export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: /*#__PURE__*/ createInstrumentationGetter(false, true)
}
export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: /*#__PURE__*/ createInstrumentationGetter(true, false)
}
export const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> =
  {
    get: /*#__PURE__*/ createInstrumentationGetter(true, true)
  }

1.1 createInstrumentationGetter

因为是代理Set和Map,在拦截它们的实例方法之前,对实例的访问,即get,这个get并非Map或Set实例的get方法,而是表示对实例的访问操作。

例如:

const map = new Map([['name', 'cc']]);

map.set('age', 18);

这里map.set()首先就是访问map的set方法,对应的key就是字符串'set',而这一步就会被代理的get程序拦截,而真正的对方法的拦截,都在相应的instrumentations里预设好了。拦截了之后,如果key在instrumentations里存在,返回预设的方法,在其中进行track和trigger操作,否则是其它属性/方法,直接返回即可,不会进行track和trigger。

const [
  mutableInstrumentations,
  readonlyInstrumentations,
  shallowInstrumentations,
  shallowReadonlyInstrumentations
] = /* #__PURE__*/ createInstrumentations()
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
  const instrumentations = shallow
    ? isReadonly
      ? shallowReadonlyInstrumentations
      : shallowInstrumentations
    : isReadonly
    ? readonlyInstrumentations
    : mutableInstrumentations
  return (
    target: CollectionTypes,
    key: string | symbol,
    receiver: CollectionTypes
  ) => {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.RAW) {
      return target
    }
    return Reflect.get(
      hasOwn(instrumentations, key) && key in target
        ? instrumentations
        : target,
      key,
      receiver
    )
  }
}

1.2 instrumentations

和baseHandlers相比,Proxy无法直接拦截Map和Set的方法的调用,而是通过get程序来拦截,再判断key是否为执行增删改查的方法,从而判断是否进行依赖收集或更新。因此,就需要先预设好,哪些key作为方法名时可以触发track和trigger。其实也就是Map和Set的那些实例方法和迭代器方法。而各种Instrumentations,就是这些预设的方法,track和trigger操作都在其中。

function createInstrumentations() {
  // 对应reactive
  const mutableInstrumentations: Record<string, Function> = {
    get(this: MapTypes, key: unknown) {
      return get(this, key)
    },
    get size() {
      return size(this as unknown as IterableCollections)
    },
    has,
    add,
    set,
    delete: deleteEntry,
    clear,
    forEach: createForEach(false, false)
  }
  // 对应shallowReactive
  const shallowInstrumentations: Record<string, Function> = {
    get(this: MapTypes, key: unknown) {
      return get(this, key, false, true)
    },
    get size() {
      return size(this as unknown as IterableCollections)
    },
    has,
    add,
    set,
    delete: deleteEntry,
    clear,
    forEach: createForEach(false, true)
  }
  // 对应readonly
  const readonlyInstrumentations: Record<string, Function> = {
    get(this: MapTypes, key: unknown) {
      return get(this, key, true)
    },
    get size() {
      return size(this as unknown as IterableCollections, true)
    },
    has(this: MapTypes, key: unknown) {
      return has.call(this, key, true)
    },
    add: createReadonlyMethod(TriggerOpTypes.ADD),
    set: createReadonlyMethod(TriggerOpTypes.SET),
    delete: createReadonlyMethod(TriggerOpTypes.DELETE),
    clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
    forEach: createForEach(true, false)
  }
  // 对应shallowReadonly
  const shallowReadonlyInstrumentations: Record<string, Function> = {
    get(this: MapTypes, key: unknown) {
      return get(this, key, true, true)
    },
    get size() {
      return size(this as unknown as IterableCollections, true)
    },
    has(this: MapTypes, key: unknown) {
      return has.call(this, key, true)
    },
    add: createReadonlyMethod(TriggerOpTypes.ADD),
    set: createReadonlyMethod(TriggerOpTypes.SET),
    delete: createReadonlyMethod(TriggerOpTypes.DELETE),
    clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
    forEach: createForEach(true, true)
  }
  // 使用 createIterableMethod 给这些 Instrumentations 挂上几个迭代器
  const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
  iteratorMethods.forEach(method => {
    mutableInstrumentations[method as string] = createIterableMethod(
      method,
      false,
      false
    )
    readonlyInstrumentations[method as string] = createIterableMethod(
      method,
      true,
      false
    )
    shallowInstrumentations[method as string] = createIterableMethod(
      method,
      false,
      true
    )
    shallowReadonlyInstrumentations[method as string] = createIterableMethod(
      method,
      true,
      true
    )
  })
  return [
    mutableInstrumentations,
    readonlyInstrumentations,
    shallowInstrumentations,
    shallowReadonlyInstrumentations
  ]
}

函数createInstrumentations分为两部分,前部分是利用已有的get、set、add、has、clear等等来得到各个instrumentations,后部分是对各个instrumentations中的迭代方法的更新。只要不是isReadonly不是真值,则无论是get、set等方法还是keys、values等迭代器接口,都在内部进行了track或trigger,当然,get、has、size等方法 和 几个迭代器方法都属于访问操作,因此内部是使用track来收集依赖,而trigger发生在增、删、改操作里,当然,也要根据isReadonly和shallow有所区分,思路基本和baseHandlers一致。

function get(
  target: MapTypes,
  key: unknown,
  isReadonly = false,
  isShallow = false
) {
  // #1772: readonly(reactive(Map)) should return readonly + reactive version
  // of the value
  target = (target as any)[ReactiveFlags.RAW]
  const rawTarget = toRaw(target)
  const rawKey = toRaw(key)
  if (!isReadonly) {
    if (key !== rawKey) {
      track(rawTarget, TrackOpTypes.GET, key)
    }
    track(rawTarget, TrackOpTypes.GET, rawKey)
  }
  const { has } = getProto(rawTarget)
  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
  if (has.call(rawTarget, key)) {
    return wrap(target.get(key))
  } else if (has.call(rawTarget, rawKey)) {
    return wrap(target.get(rawKey))
  } else if (target !== rawTarget) {
    // #3602 readonly(reactive(Map))
    // ensure that the nested reactive `Map` can do tracking for itself
    target.get(key)
  }
}
function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
  const target = (this as any)[ReactiveFlags.RAW]
  const rawTarget = toRaw(target)
  const rawKey = toRaw(key)
  if (!isReadonly) {
    if (key !== rawKey) {
      track(rawTarget, TrackOpTypes.HAS, key)
    }
    track(rawTarget, TrackOpTypes.HAS, rawKey)
  }
  return key === rawKey
    ? target.has(key)
    : target.has(key) || target.has(rawKey)
}
function size(target: IterableCollections, isReadonly = false) {
  target = (target as any)[ReactiveFlags.RAW]
  !isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
  return Reflect.get(target, 'size', target)
}
function add(this: SetTypes, value: unknown) {
  value = toRaw(value)
  const target = toRaw(this)
  const proto = getProto(target)
  const hadKey = proto.has.call(target, value)
  if (!hadKey) {
    target.add(value)
    trigger(target, TriggerOpTypes.ADD, value, value)
  }
  return this
}
function set(this: MapTypes, key: unknown, value: unknown) {
  value = toRaw(value)
  const target = toRaw(this)
  const { has, get } = getProto(target)
  let hadKey = has.call(target, key)
  if (!hadKey) {
    key = toRaw(key)
    hadKey = has.call(target, key)
  } else if (__DEV__) {
    checkIdentityKeys(target, has, key)
  }
  const oldValue = get.call(target, key)
  target.set(key, value)
  if (!hadKey) {
    trigger(target, TriggerOpTypes.ADD, key, value)
  } else if (hasChanged(value, oldValue)) {
    trigger(target, TriggerOpTypes.SET, key, value, oldValue)
  }
  return this
}
function deleteEntry(this: CollectionTypes, key: unknown) {
  const target = toRaw(this)
  const { has, get } = getProto(target)
  let hadKey = has.call(target, key)
  if (!hadKey) {
    key = toRaw(key)
    hadKey = has.call(target, key)
  } else if (__DEV__) {
    checkIdentityKeys(target, has, key)
  }
  const oldValue = get ? get.call(target, key) : undefined
  // forward the operation before queueing reactions
  const result = target.delete(key)
  if (hadKey) {
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  return result
}
function clear(this: IterableCollections) {
  const target = toRaw(this)
  const hadItems = target.size !== 0
  const oldTarget = __DEV__
    ? isMap(target)
      ? new Map(target)
      : new Set(target)
    : undefined
  // forward the operation before queueing reactions
  const result = target.clear()
  if (hadItems) {
    trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
  }
  return result
}

1.3 createIterableMethod

这里稍微提一下createIterableMethod,用于利用Map和Set本身的迭代器方法,并做了一点修改,在其中加入了track来收集依赖。

function createIterableMethod(
  method: string | symbol,
  isReadonly: boolean,
  isShallow: boolean
) {
  return function (
    this: IterableCollections,
    ...args: unknown[]
  ): Iterable & Iterator {
    const target = (this as any)[ReactiveFlags.RAW]
    const rawTarget = toRaw(target)
    const targetIsMap = isMap(rawTarget)
    const isPair =
      method === 'entries' || (method === Symbol.iterator && targetIsMap)
    const isKeyOnly = method === 'keys' && targetIsMap
    const innerIterator = target[method](...args)
    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
    !isReadonly &&
      track(
        rawTarget,
        TrackOpTypes.ITERATE,
        isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
      )
    // return a wrapped iterator which returns observed versions of the
    // values emitted from the real iterator
    return {
      // iterator protocol
      next() {
        const { value, done } = innerIterator.next()
        return done
          ? { value, done }
          : {
              value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
              done
            }
      },
      // iterable protocol
      [Symbol.iterator]() {
        return this
      }
    }
  }
}

小结

分析完各个部分,可以看到,无论是baseHandlers还是collectionHandlers,思路都是一致的。

但是collectionHandlers只有get这一个代理程序,通过拦截到的key判断是否是Map和Set实例自带的增删改查的方法,从而返回预设好的hack版本的方法或原本的属性值,然后继续后续的操作。在hack版本的方法里进行track和trigger。

以上就是Vue3 源码分析reactive readonly实例的详细内容,更多关于Vue3 reactive readonly的资料请关注我们其它相关文章!

(0)

相关推荐

  • vue3中defineProps传值使用ref响应式失效详解

    子组件接收父组件的传参. 父组件: <template> <Son :data="data"/> </template> <script setup> import { ref } from "vue"; let data = ref('hello') setTimeout(() => { data.value = 'how are you doing' }, 2000) </script> 子组件:

  • vue3响应式Proxy与Reflect的理解及基本使用实例详解

    目录 正文 理解Proxy与Reflect Proxy Reflect 实践示例 正文 在第四章中,作者讲述了Vue.js中响应式系统的设计与实现,这一块其实是整个框架的基石,也是MVVM中,ViewModel(VM)的重要组成部分. 其实在上一章中我已经感觉很难了,有一些操作作者也只是几笔带过,却很值得我们思考.这一张中,我们将目光着重于响应式数据本身,来完善上一章中我们的demo. 理解Proxy与Reflect vue3的响应式离不开Proxy,说到Proxy则离不开Reflect.这两个

  • vue3响应式实现readonly从零开始教程

    目录 前言 readonly的实现 重构 结束 前言 前面的章节我们把 effect 部分大致讲完了,这部分我们来讲 readonly以及重构一下reactive. readonly的实现 it("happy path", () => { console.warn = vi.fn(); const original = { foo: 1, }; const observed = readonly({ foo: 1, }); expect(original).not.toBe(ob

  • vue3 reactive响应式依赖收集派发更新原理解析

    目录 proxy 依赖收集 currentEffect 派发更新 总结 proxy vue3的响应式实现依旧是依赖收集与派发更新,本节乃至后面涉及的代码都是经过简化,文章目的是讲解原理,直接贴源码会很枯燥 vue3已经从Object.property更换成Proxy,Proxy相比于前者可以直接监听对象数组,对于深层次的对象和数组,会把触发对应getter,然后去递归进行依赖收集,并不是直接像vue2暴力那样递归,总体而言性能更好 对reactive传进来的对象进行Proxy进行劫持在内部进行依

  • vue3响应式Object代理对象的读取示例详解

    目录 正文 读取属性 xx in obj for ... in 正文 从这一章开始,作者将更新深入的讲解响应式,尤其是vue3响应式的具体的实现.其实在前面一章,如果你仔细阅读,你是可以实现一个简单的响应式函数的,类似于@vue/reactive,当然那只是个demo,是个玩具,我能不能在生产环境上去使用的,它差了太多功能和边界条件. 现在,我们才是真正的深入@vue/reactive. 在vue中,obj.a是一个读取操作,但是仔细想来,读取这个操作很宽泛. obj.a // 访问一个属性 '

  • vue3 响应式对象如何实现方法的不同点

    目录 vue3响应式对象实现方法的不同点 Vue2和Vue3响应式原理对比 响应式原理实现逻辑 Vue2响应式原理简化 Vue2响应式原理弊端 Vue3响应式原理简化 vue3响应式对象实现方法的不同点 vue 响应式对象是利用 defindeProperty 实现,vue3 则是使用 Proxy 来实现响应式的. 二者,虽然都实现了 响应式的功能,但实现方式不一样,解决问题也有些不同. vue2 中,如果想要实现,数组元素的更新,则是需要 用到检测更新的方法 set 之类的,但 vue3的响应

  • Vue3 源码分析reactive readonly实例

    目录 引言 一.reactive 和 readonly 1. reactive相关类型 2. 相关全局变量与方法 3. reactive函数 4. 造物主createReactiveObject 5. shallowReactive.readonly和shallowReadonly 二.对应的 Handlers 1. baseHandlers 1.1 reactive 1.2 readonly 1.3 shallowReactive 1.4 shallowReadonly 2. cellecti

  • Vue3源码分析侦听器watch的实现原理

    目录 watch 的本质 watch 的函数签名 侦听多个源 侦听单一源 watch 的实现 watch 函数 source 参数 cb 参数 options 参数 doWatch 函数 doWatch 函数签名 初始化变量 递归读取响应式数据 定义清除副作用函数 封装 scheduler 调度函数 设置 job 的 allowRecurse 属性 flush 选项指定回调函数的执行时机 创建副作用函数 执行副作用函数 返回匿名函数,停止侦听 总结 watch 的本质 所谓的watch,其本质就

  • Vue3源码解析watch函数实例

    目录 引言 一.watch参数类型 1. 选项options 2. 回调cb 3. 数据源source 二.watch函数 三.watch的核心:doWatch 函数 引言 想起上次面试,问了个古老的问题:watch和computed的区别.多少有点感慨,现在已经很少见这种耳熟能详的问题了,网络上八股文不少.今天,我更想分享一下从源码的层面来区别这八竿子打不着的两者.本篇针对watch做分析,下一篇分析computed. 一.watch参数类型 我们知道,vue3里的watch接收三个参数:侦听

  • Vue3源码分析reactivity实现方法示例

    目录 深入分析对于map.set.weakMap.weakSet的响应式拦截 (1).mutableInstrumentations (2).shallowInstrumentations (3).readonlyInstrumentations (4).shallowReadonlyInstrumentations ref.computed等方法的实现 (1).ref与shallowRef源码解析 (2).toRefs (4).computed (5)其他api源码 最后总结: 深入分析对于m

  • Vue3源码分析组件挂载初始化props与slots

    目录 前情提要 初始化组件 (1).setupComponent (2).initProps (3).initSlots 额外内容 总结 前情提要 上文我们分析了挂载组件主要调用了三个函数: createComponentInstance(创建组件实例).setupComponent(初始化组件).setupRenderEffect(更新副作用).并且上一节中我们已经详细讲解了组件实例上的所有属性,还包括emit.provide等的实现.本文我们将继续介绍组件挂载流程中的初始化组件. 本文主要内

  • Vue源码分析之Vue实例初始化详解

    这一节主要记录一下:Vue 的初始化过程 以下正式开始: Vue官网的生命周期图示表 重点说一下 new Vue()后的初始化阶段,也就是created之前发生了什么. initLifecycle 阶段 export function initLifecycle (vm: Component) { const options = vm.$options // locate first non-abstract parent let parent = options.parent if (pare

  • 源码分析Vue3响应式核心之effect

    目录 一.effect用法 1.基本用法 2.lazy属性为true 3.options中包含onTrack 二.源码分析 1.effect方法的实现 2.ReactiveEffect函数源码 三.依赖收集相关 1.如何触发依赖收集 2.track源码 3.trackEffects(dep, eventInfo)源码解读 四.触发依赖 1.trigger依赖更新 2.triggerEffects(deps[0], eventInfo) 3.triggerEffect(effect, debugg

  • JAVA 枚举单例模式及源码分析的实例详解

    JAVA 枚举单例模式及源码分析的实例详解 单例模式的实现有很多种,网上也分析了如今实现单利模式最好用枚举,好处不外乎三点: 1.线程安全 2.不会因为序列化而产生新实例 3.防止反射攻击但是貌似没有一篇文章解释ENUM单例如何实现了上述三点,请高手解释一下这三点: 关于第一点线程安全,从反编译后的类源码中可以看出也是通过类加载机制保证的,应该是这样吧(解决) 关于第二点序列化问题,有一篇文章说枚举类自己实现了readResolve()方法,所以抗序列化,这个方法是当前类自己实现的(解决) 关于

  • Android实例HandlerThread源码分析

    HandlerThread 简介: 我们知道Thread线程是一次性消费品,当Thread线程执行完一个耗时的任务之后,线程就会被自动销毁了.如果此时我又有一 个耗时任务需要执行,我们不得不重新创建线程去执行该耗时任务.然而,这样就存在一个性能问题:多次创建和销毁线程是很耗 系统资源的.为了解这种问题,我们可以自己构建一个循环线程Looper Thread,当有耗时任务投放到该循环线程中时,线程执行耗 时任务,执行完之后循环线程处于等待状态,直到下一个新的耗时任务被投放进来.这样一来就避免了多次

随机推荐