1. mutableHandlers是什么?
回顾一下在reactive.ts章节,当我们调用Vue的reactive方法时,在Vue底层实际上是创建并返回了一个proxy对象,创建这个代理对象时,我们传入的处理器是 mutableHandlers:
export function reactive(target: object) {
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
mutableHandlers 实际上是 MutableReactiveHandler 类的一个实例:
//reactive.ts文件中引用:
import { mutableHandlers } from './baseHandlers'
//baseHandler.ts文件定义:
export const mutableHandlers: ProxyHandler<object> =
/*#__PURE__*/ new MutableReactiveHandler()
这表明,每当我们将一个对象通过 reactive 转换成响应式对象时,实际上就是在应用 MutableReactiveHandler 类提供的逻辑。
2. MutableReactiveHandler 类的定义
MutableReactiveHandler 类在 Vue 3 的响应式系统中扮演着核心角色,它继承BaseReacriverHandler类,并且扩展了处理非只读响应式对象的各种操作,包括属性的设置(set)、删除(deleteProperty)、存在性检查(has)和键的枚举(ownKeys)等。
3. 核心方法解析
class MutableReactiveHandler extends BaseReactiveHandler {
/**
* 构造函数接受一个isShallow参数,用于决定创建的响应式对象是否为浅响应式。这影响着对象内部属性的响应式行为。
*/
constructor(isShallow = false) {
super(false, isShallow)
}
/**
* set方法拦截对响应式对象属性的赋值操作。
* 它首先比较新值和旧值,如果有必要,会进行相应的更新,并触发相应的副作用(如视图更新)。
* 特别地,如果属性值从ref变为非ref,或相反,这个方法会进行特殊处理。
*/
set(
target: object,
key: string | symbol,
value: unknown,
receiver: object,
): boolean {
let oldValue = (target as any)[key]
if (!this._isShallow) {
/**
* 深响应式对象的处理逻辑
*/
// 1.处理旧值和新值
// 处理旧值和新值:首先判断当前属性的旧值是否是只读的
const isOldValueReadonly = isReadonly(oldValue)
if (!isShallow(value) && !isReadonly(value)) {
/**
* 1.2 转换旧值和新值为原始对象:
* 如果新值既不是浅响应式的也不是只读的,那么会将旧值和新值都转换为它们的原始对象(如果它们是响应式的或只读的)。
* 这一步确保在对比和设置属性值时,我们操作的是原始数据,而不是响应式代理。
*/
oldValue = toRaw(oldValue)
value = toRaw(value)
}
// 2.处理ref值
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
/**
* 2.1 条件检查:这一部分的逻辑仅在目标不是数组、旧值是一个ref且新值不是ref的情况下执行。
* 这是因为Vue允许你将一个ref直接赋值给对象的属性,但如果旧值是ref而新值不是,需要特殊处理。
*/
if (isOldValueReadonly) {
/**
* 2.2 只读ref的处理:如果旧值是一个只读的ref,那么不允许修改它的值,因此函数返回false表示设置操作失败。
*/
return false
} else {
/**
* 2.3 更新ref的值:如果旧值是一个普通的ref(不是只读的),那么将直接更新这个ref的value属性为新值,并返回true表示设置操作成功。
*/
oldValue.value = value
return true
}
}
} else {
/**
* 当一个响应式对象处于浅响应模式时,对其属性进行设置操作,设置的值将不会自动转换为响应式对象。
* 即,不论你设置的值原本是否是响应式的,它都将按原样(as-is)被设置到对象上,Vue不会对这个值进行深层的响应式转换。
*/
}
//3. 判断属性是否已存在
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
// 3.1 设置属性值:使用Reflect.set方法来实际上设置目标对象的属性值
const result = Reflect.set(target, key, value, receiver)
/**
* 属性值发生变化时触发更新
* toRaw函数用来获取响应式对象背后的原始对象
* 这个检查确保只有当操作的目标对象就是接收对象的原始对象时,才会触发更新。这是为了避免在原型链上误触发更新
*/
if (target === toRaw(receiver)) {
if (!hadKey) {
//如果属性之前不存在(对于新添加的属性),则使用trigger函数触发ADD操作的更新。
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
//如果属性已存在且新值与旧值不相同(使用hasChanged函数检查),则触发SET操作的更新。
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
//返回结果:告诉调用者属性是否成功被设置
return result
}
/**
* deleteProperty方法拦截对响应式对象属性的删除操作。如果属性存在且成功删除,会触发副作用。
*/
deleteProperty(target: object, key: string | symbol): boolean {
const hadKey = hasOwn(target, key)
const oldValue = (target as any)[key]
const result = Reflect.deleteProperty(target, key)
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}
/**
* has方法拦截属性存在性的检查操作。这个操作也会被用于依赖追踪,以便在未来这个属性被添加时能触发更新。
*/
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
}
/**
* ownKeys方法拦截对象键的枚举操作。这在进行for...in循环或Object.keys调用时特别有用,同样会进行依赖追踪。
*/
ownKeys(target: object): (string | symbol)[] {
track(
target,
TrackOpTypes.ITERATE,
isArray(target) ? 'length' : ITERATE_KEY,
)
return Reflect.ownKeys(target)
}
}
4. 举个例子
当我们通过 reactive 方法创建响应式对象并对其属性进行操作时,MutableReactiveHandler 的各种方法将被调用来确保响应式行为的正确执行:
const reactiveObject = reactive({ a: 1 });
reactiveObject.a = 2;
在这个过程中:
4.1 关于target和receiver:
target是原始的对象,即通过reactive包装之前的对象,这里是{ a: 1 }。
receiver是reactiveObject,即reactive({ a: 1 })的返回值,一个代理对象,它代表了target的响应式版本。
4.2 获取旧值:
当尝试通过reactiveObject.a = 2;设置属性时,set方法会被调用。此时,会从target中获取属性a的旧值,这里旧值是1。
4.3 转换为原始对象:
如果旧值或新值是通过ref或reactive包装的响应式对象,toRaw会将其转换回原始对象。但在这个例子中,旧值1和新值2都是普通数值,没有使用ref包装,所以这一步跳过。
4.4 处理ref值:
这个步骤主要是为了处理当目标对象的某个属性是一个ref,而新设置的值不是ref的情况。但在这个例子中,既没有涉及到ref,所以这部分逻辑不会被执行。
4.5 判断属性是否已存在并设置新值:
通过Reflect.set直接设置属性值,这一步不仅会更新对象的属性,还会检查这个属性是否之前存在于对象中。
hadKey用于判断属性在操作之前是否存在,这对于后续决定是触发ADD还是SET类型的更新很重要。
4.6 触发更新:
根据属性是否新添加以及值是否改变,使用trigger函数触发相应的更新。在这个例子中,因为a属性在target上已存在,且新值与旧值不同,所以会触发TriggerOpTypes.SET类型的更新。
5. 小结
MutableReactiveHandler 是 Vue 3 响应式系统的核心组成之一,它通过 Proxy API 拦截对响应式对象的操作,并以此实现数据到视图的自动响应。通过继承 BaseReactiveHandler 并扩展其功能,Vue 能够提供一个强大而灵活的响应式系统,支持开发者构建高效且响应迅速的 Web 应用。