[vue3] Vue3源码阅读笔记 reactivity

cnblogs 2024-07-30 08:11:01 阅读 87

vue3的reactivity模块的baseHandlers主要为普通对象和数组的Proxy实现了拦截操作的处理,通过3个类分别实现了getter和setter等操作的拦截,并对一些数组原型上的方法做了特殊处理,使得数组操作可以更好地适配vue的响应式系统。

源码位置:https://github.com/vuejs/core/blob/main/packages/reactivity/src/baseHandlers.ts

<code>baseHandler用于处理对象、数组类型的gettersetter

这个文件中主要有两个函数,和三个类。

arrayInstrucmentationshasOwnProperty这两个函数主要起到辅助作用;

3个类:

  • BaseReactiveHandler:负责处理getter
  • MutableReactiveHandlerReadonlyReactiveHandler:负责处理setter

graph LR

A[ProxyHandler] --> B[BaseReactiveHandler]

B --> C[MutableReactiveHandler] & D[ReadonlyReactiveHandler]

依赖

依赖比较零碎,大致分为以下几种:

  1. 用于判断数据类型;
  2. Vue内置的FlagType,用来标记对象或者操作的类型;
  3. 暂停与重置依赖追踪和任务调度的方法;
  4. 其它零碎的比如:
    • warning:在控制台输出警告信息;
    • makeMap:传入一个用,分隔的多个key组成的字符串,返回一个has函数用于检查后续传入的key是否存在于一开始传入的字符串中。

arrayInstrucmentations

arrayInstrumentations这个对象用于记录一些处理过的数组方法(拦截操作),通过createArrayInstrumentations构建后大概长这样:

{

...

'push': function(this, ...args){...},

'indexOf': function(this, ...args){...},

...

}

拦截这些方法的原因

  • ['includes', 'indexOf', 'lastIndexOf']:数组中可能包含响应式对象,这几个方法是需要比较数组元素的,直接比较可能会出错,因此需要拦截这些方法,在比较的过程中考虑使用toRaw转成原始对象进行比较。

  • ['push', 'pop', 'shift', 'unshift', 'splice']:这些方法会改变数组的长度length属性,从而触发与length属性相关的effect,而effect中如果又有这些方法,那么就会导致死循环。因此,这些方法需要被拦截做特殊处理,在执行这些方法的时候要暂停依赖的追踪和调度。

    相关的issue是:fix(reactivity): some mutation methods of Array cause infinite recursion by unbyte · Pull Request #2138 · vuejs/core (github.com)

    image-20240730004826476

源码与注释

<code>const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()

function createArrayInstrumentations() {

const instrumentations: Record<string, Function> = {}

// 给需要处理可能包含响应式值的数组方法增加拦截逻辑

;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {

instrumentations[key] = function (this: unknown[], ...args: unknown[]) {

// 将 this 转换为原始数组

const arr = toRaw(this) as any

// 追踪数组每个元素的 GET 操作

for (let i = 0, l = this.length; i < l; i++) {

track(arr, TrackOpTypes.GET, i + '')

}

// 先使用原始参数(可能是响应式的)运行原方法

const res = arr[key](...args)

// 如果结果是 -1 或 false,说明没有找到或不匹配,再次使用原始值运行一次

if (res === -1 || res === false) {

return arr[key](...args.map(toRaw))

} else {

return res

}

}

})

// 拦截会改变数组长度的方法,避免长度变化被追踪,从而防止出现无限循环的问题 (#2137)

;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {

instrumentations[key] = function (this: unknown[], ...args: unknown[]) {

// 暂停追踪

pauseTracking()

// 暂停调度

pauseScheduling()

// 使用原始数组运行原方法

const res = (toRaw(this) as any)[key].apply(this, args)

// 重置调度

resetScheduling()

// 重置追踪

resetTracking()

return res

}

})

return instrumentations

}

hasOwnProperty

Vue对hasOwnProperty做了特殊处理,主要在于处理以下问题:

  • 确保需要检查的key只能是Symbol类型或string类型,如果是其它,则通过String()转为字符串;
  • 使用toRaw在原始对象上查询key是否存在;
  • 使用track追踪key

源码

function hasOwnProperty(this: object, key: unknown) {

// #10455 hasOwnProperty may be called with non-string values

if (!isSymbol(key)) key = String(key)

const obj = toRaw(this)

track(obj, TrackOpTypes.HAS, key)

return obj.hasOwnProperty(key as string)

}

BaseReactiveHandler

这个类主要负责配置getter,当reactiveAPI包装的响应式对象的某个key被读取时,会触发这里的getter

  • 如果读取的key是内置的ReactiveFlags,返回相应的值;
  • 如果target是一个数组,那么需要应用上述arrayInstrucmentations记录的处理过的数组;
  • 如果keyhasOwnProperty,返回上述特殊处理过的hasOwnProperty
  • key记录依赖。

class BaseReactiveHandler implements ProxyHandler<Target> {

constructor(

protected readonly _isReadonly = false, // 是否只读

protected readonly _isShallow = false, // 是否浅层响应式

) {}

get(target: Target, key: string | symbol, receiver: object) {

const isReadonly = this._isReadonly,

isShallow = this._isShallow

// 处理 ReactiveFlags 特殊标志

if (key === ReactiveFlags.IS_REACTIVE) {

// 判断目标是否是响应式的

return !isReadonly

} else if (key === ReactiveFlags.IS_READONLY) {

// 判断目标是否是只读的

return isReadonly

} else if (key === ReactiveFlags.IS_SHALLOW) {

// 判断目标是否是浅层响应的

return isShallow

} else if (key === ReactiveFlags.RAW) {

// 处理 RAW 标志

if (

receiver ===

(isReadonly

? isShallow

? shallowReadonlyMap

: readonlyMap

: isShallow

? shallowReactiveMap

: reactiveMap

).get(target) ||

// receiver 不是响应式代理,但具有相同的原型

// 这意味着 receiver 是响应式代理的用户代理

Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)

) {

return target

}

// 提前返回 undefined

return

}

const targetIsArray = isArray(target)

// 处理非只读的情况

if (!isReadonly) {

if (targetIsArray && hasOwn(arrayInstrumentations, key)) {

// 对于数组的特殊方法,使用上述的方法拦截处理

return Reflect.get(arrayInstrumentations, key, receiver)

}

if (key === 'hasOwnProperty') {

// 特殊处理 hasOwnProperty 方法

return hasOwnProperty

}

}

// 默认的 Reflect.get 操作

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 (isShallow) {

return res

}

// 处理 Ref 类型的值

if (isRef(res)) {

// 如果是数组并且键是整数,则跳过 unwrap

return targetIsArray && isIntegerKey(key) ? res : res.value

}

// 处理对象类型的值,将返回的值转化为代理对象

if (isObject(res)) {

// 将返回的值转换为代理对象,避免循环依赖

return isReadonly ? readonly(res) : reactive(res)

}

return res

}

}

MutableReactiveHandler

这个类实现了对setdeletePropertyhasownKeys的拦截

class MutableReactiveHandler extends BaseReactiveHandler {

constructor(isShallow = false) {

super(false, isShallow) // 调用父类构造函数,设置只读标志为 false

}

set(

target: object,

key: string | symbol,

value: unknown,

receiver: object,

): boolean {

let oldValue = (target as any)[key] // 获取目标对象中原有的值

if (!this._isShallow) {

// 如果不是浅层响应,进行深层处理

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) {

// 如果原有值是只读的,返回 false

return false

} else {

oldValue.value = value // 更新 ref 的值

return true

}

}

} else {

// 在浅层模式中,直接设置对象,不考虑其是否为响应式

}

// 判断目标对象是否之前已经有这个键

const hadKey =

isArray(target) && isIntegerKey(key)

? Number(key) < target.length

: hasOwn(target, key)

const result = Reflect.set(target, key, value, receiver) // 使用 Reflect 设置值

// 判断target是否是实际被修改的对象

if (target === toRaw(receiver)) {

if (!hadKey) {

// 如果之前没有这个键,触发 ADD 操作

trigger(target, TriggerOpTypes.ADD, key, value)

} else if (hasChanged(value, oldValue)) {

// 如果键已经存在且新值不同,触发 SET 操作

trigger(target, TriggerOpTypes.SET, key, value, oldValue)

}

}

return result

}

deleteProperty(target: object, key: string | symbol): boolean {

const hadKey = hasOwn(target, key) // 判断目标对象是否有这个键

const oldValue = (target as any)[key] // 获取原有值

const result = Reflect.deleteProperty(target, key) // 使用 Reflect 删除属性

if (result && hadKey) {

// 如果删除成功且目标对象之前有这个键,触发 DELETE 操作

trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)

}

return result

}

has(target: object, key: string | symbol): boolean {

const result = Reflect.has(target, key) // 使用 Reflect 检查是否存在该键

if (!isSymbol(key) || !builtInSymbols.has(key)) {

// 如果键不是内置符号,追踪 HAS 操作

track(target, TrackOpTypes.HAS, key)

}

return result

}

ownKeys(target: object): (string | symbol)[] {

// 追踪 ITERATE 操作,用于获取对象的所有键

track(

target,

TrackOpTypes.ITERATE,

isArray(target) ? 'length' : ITERATE_KEY,

)

return Reflect.ownKeys(target) // 使用 Reflect 获取对象的所有键

}

}

解析set里的trigger时机:

// 判断target是否是实际被修改的对象

if (target === toRaw(receiver)) {

if (!hadKey) {

// 如果之前没有这个键,触发 ADD 操作

trigger(target, TriggerOpTypes.ADD, key, value)

} else if (hasChanged(value, oldValue)) {

// 如果键已经存在且新值不同,触发 SET 操作

trigger(target, TriggerOpTypes.SET, key, value, oldValue)

}

}

如果target !== toRaw(receiver)说明此时不是在对象上修改它本身的属性,而是通过原型链上的其它对象。这种情况不会触发更新。

ReadonlyReactiveHandler

这个类比较简单,主要是拦截setdeleteProperty这两个会改变对象的操作。

开发模式下会在控制台输出警告。

注意setdeleteProperty操作会返回true,这是为了符合Proxy规范:即使某些操作被拦截并不实际改变对象的状态,仍然需要返回一个布尔值以指示操作的成功或失败。

class ReadonlyReactiveHandler extends BaseReactiveHandler {

constructor(isShallow = false) {

super(true, isShallow) // 调用父类构造函数,将 isReadonly 设置为 true,表示对象是只读的

}

set(target: object, key: string | symbol, value: unknown): boolean {

if (__DEV__) {

warn(

`Set operation on key "${String(key)}" failed: target is readonly.`,

target,

)

}

return true // 返回 true,表示设置操作被忽略

}

deleteProperty(target: object, key: string | symbol): boolean {

if (__DEV__) {

warn(

`Delete operation on key "${String(key)}" failed: target is readonly.`,

target,

)

}

return true // 返回 true,表示删除操作被忽略

}

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。