Vue3的reactive和readonly实例如何改写为长尾?

2026-04-02 21:061阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计4552个文字,预计阅读时间需要19分钟。

Vue3的reactive和readonly实例如何改写为长尾?

目录+引言+一、reactive相关类型

1.reactive相关类型

1. reactive相关类型 2. 相关全局变量与方法 3. reactive函数 4. 原型方法createReactiveObject 5. shallowReactive、readonly和shallowReadonly

二、对应的Handlers

1.baseHandlers

1.1 baseHandlers

目录
  • 引言
  • 一、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) 。

    Vue3的reactive和readonly实例如何改写为长尾?

    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的资料请关注自由互联其它相关文章!

    本文共计4552个文字,预计阅读时间需要19分钟。

    Vue3的reactive和readonly实例如何改写为长尾?

    目录+引言+一、reactive相关类型

    1.reactive相关类型

    1. reactive相关类型 2. 相关全局变量与方法 3. reactive函数 4. 原型方法createReactiveObject 5. shallowReactive、readonly和shallowReadonly

    二、对应的Handlers

    1.baseHandlers

    1.1 baseHandlers

    目录
    • 引言
    • 一、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) 。

      Vue3的reactive和readonly实例如何改写为长尾?

      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的资料请关注自由互联其它相关文章!