reactive
reactive
用于返回一个普通响应式代理(非只读或者禁止响应式代理)。
reactive
代码分析的入口就从 vue
提供的 reactive
函数开始,先来看看 vue
官方的组合式 api
手册中对这个函数的说明:
接收一个普通对象然后返回该普通对象的响应式代理。
const obj = reactive({ count: 0 })
响应式转换是“深层的”:会影响对象内部所有嵌套的属性。基于 ES2015
的 Proxy
实现,返回的代理对象不等于原始对象。
前文我们说过reactive
的实现是由 proxy
加 effect
组合,先来看一下 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 (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
注意: 对一个
readonly
的代理对象使用reactive
,会返回这个只读代理对象本身。
const rOnly = readonly({ count: 1 }); const stillROnly = reactive(ronly); stillROnly.count++; // Cannot assign to 'count' because it is a read-only property.
createReactiveObject
其实本身 API
是很简单的,传入一个对象,返回一个其普通响应式代理对象,创建的方法是createReactiveObject
:
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[ReactiveFlags.RAW 证明已经是一个响应式代理,除非是对一个 reactive 对象调用 readonly 否则直接返回;
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 a whitelist of 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
最后返回的是一个 proxy
对象即 target
的响应式代理,具体看看这个函数的判断流程:
-
如果传入一个原始类型(
primitive
),原始类型到响应式代理的转换要用ref
,reactive
无法处理所以返回原对象; -
通过
target[ReactiveFlags.RAW]
判断传入的target
已经是是某种响应式代理类型,除非这个响应式对象原本是普通响应式代理而现在要设置为只读响应式代理,否则直接返回,也就是下面这几种情况会直接返回原代理:reactive(readonly) -> readonly readonly(readonly) -> readonly reactive(reactive) -> reactive
-
尝试在
proxyMap
查看有target
对应的响应式代理,有则返回; -
最后通过
getTargetType
(全局变量中有提到) 判断target
是否属于能转换为响应式代理的类型,如果是则通过proxy
进行转换,并且在proxyMap
上做缓存;- 根据
target
类型的不同(object/Array or Map/Set/WeakMap/WeakSet
)使用的proxyHandler
也不同。
- 根据
readonly
readonly
返回对象的只读代理,传入一个对象(响应式或普通)或 ref
,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。
触发只读代理的依赖通常是通过修改其引用的响应式代理:
const original = reactive({ count: 0 })
const copy = readonly(original)
// 依赖追踪
watchEffect(() => console.log(copy.count))
// original 上的修改会触发 copy 上的侦听
original.count++
// 无法修改 copy 并会被警告
copy.count++ // warning!
readonly
的函数定义没什么好说的,就是改了几个参数:
/**
* Creates a readonly copy of the original object. Note the returned copy is not
* made reactive, but `readonly` can be called on an already reactive object.
*/
export function readonly<T extends object>(
target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
return createReactiveObject(
target,
true,
readonlyHandlers,
readonlyCollectionHandlers,
readonlyMap
)
}
在 ts
中 vue
对 readonly
返回的类型做了约束,所以修改属性会在编译期间报错。
这个 DeepReadonly
类型的实现也很有意思。显示对 Builtin
内置类型做检查,这种类型不需要深度递归只解 readonly
包裹。然后对于 Map/Set/WeakSet/WeakMap/Promise
通过 infer
取出嵌套的类型然后给它加上 DeepReadonly
,对于 Object
遍历所有属性然后递归 DeepReadonly
:
type Primitive = string | number | boolean | bigint | symbol | undefined | null
type Builtin = Primitive | Function | Date | Error | RegExp
export type DeepReadonly<T> = T extends Builtin
? T
: T extends Map<infer K, infer V>
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
: T extends ReadonlyMap<infer K, infer V>
? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
: T extends WeakMap<infer K, infer V>
? WeakMap<DeepReadonly<K>, DeepReadonly<V>>
: T extends Set<infer U>
? ReadonlySet<DeepReadonly<U>>
: T extends ReadonlySet<infer U>
? ReadonlySet<DeepReadonly<U>>
: T extends WeakSet<infer U>
? WeakSet<DeepReadonly<U>>
: T extends Promise<infer U>
? Promise<DeepReadonly<U>>
: T extends {}
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: Readonly<T>
readonly(reactive(obj))
唯一会产生效果的嵌套响应式调用就是 readonly(reactive(obj))
,这个东西返回的到底是什么呢?
type nested = {
getter: readonlyGetter,
setter: readonlySetter,
[ReacticeFlags.RAW]: {
getter: reactiveGetter,
setter: reactiveSetter,
[ReacticeFlags.RAW]: obj,
} as ReactiveProxy
} as ReadonlyProxy
所以说访问 rorv
或者 rv
的 getter
都会收集依赖到同一个对象属性的 deps
,而 setter
触发的也是同一个对象属性的 deps
中的 effects
。
shallowReactive
shallowReactive
只为某个对象的私有(第一层)属性创建浅层的响应式代理,不会对“属性的属性”做深层次、递归地响应式代理,而只是保留原样。
const state = shallowReactive({
foo: 1,
nested: {
bar: 2,
},
})
// 变更 state 的自有属性是响应式的
state.foo++
// ...但不会深层代理
isReactive(state.nested) // false
state.nested.bar++ // 非响应式
同样调用的 createReactiveObject
,参数也没什么好说的:
export function shallowReactive<T extends object>(target: T): T {
return createReactiveObject(
target,
false,
shallowReactiveHandlers,
shallowCollectionHandlers,
shallowReactiveMap
)
}
shallowReadonly
只为某个对象的自有(第一层)属性创建浅层的只读响应式代理,不会做深层次、递归地代理,深层次的属性并不是只读的。
const state = shallowReadonly({
foo: 1,
nested: {
bar: 2,
},
})
// 变更 state 的自有属性会失败
state.foo++
// ...但是嵌套的对象是可以变更的
isReadonly(state.nested) // false
isReactive(state.nested) // false
state.nested.bar++ // 嵌套属性依然可修改
同样调用的 createReactiveObject
,参数也没什么好说的:
export function shallowReadonly<T extends object>(
target: T
): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> {
return createReactiveObject(
target,
true,
shallowReadonlyHandlers,
shallowReadonlyCollectionHandlers,
shallowReadonlyMap
)
}
UtilFunc
isReadonly
检查一个对象是否是由 readonly
创建的只读代理,就是通过 ReactiveFlags.IS_READONLY
这个标志位,其实这个标志位是通过在 getter
里设置拦截实现的:
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
// const get = /*#__PURE__*/ createGetter()
// function createGetter(isReadonly = false, shallow = false) {
// return function get(target: Target, key: string | symbol, receiver: object) {
// if (key === ReactiveFlags.IS_REACTIVE) return !isReadonly
// else if (key === ReactiveFlags.IS_READONLY) return isReadonly
// else if (key === ReactiveFlags.RAW) return target
isReactive
检查一个对象是否是由 reactive
创建的响应式代理,分两种情况:
(value as Target)[ReactiveFlags.IS_REACTIVE]
:说明target
就是一个reactive
对象,返回true
(value as Target)[ReactiveFlags.IS_READONLY]
&&isReactive((value as Target)[ReactiveFlags.RAW])
说明是一个readonly(reactive)
的嵌套对象,也返回true
;
export function isReactive(value: unknown): boolean {
if (isReadonly(value)) {
return isReactive((value as Target)[ReactiveFlags.RAW])
}
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}
举个例子:
const rv = reactive({ count: 1 });
const rorv = readonly(rv);
console.log('readonly', isReadonly(rorv)); // readonly true
console.log('reactive', isReactive(rorv)); // reactive true
isProxy
查看是否设置任意一种代理:
export function isProxy(value: unknown): boolean {
return isReactive(value) || isReadonly(value)
}
toRaw
toRaw
返回代理对象对应的实际对象,如果嵌套了多层代理,返回最终的实际对象,RAW
标志位也是通过在 getter
里设置拦截实现的。
export function toRaw<T>(observed: T): T {
return (
(observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed
)
}
markRaw
显式标记一个对象为“永远不会转为响应式代理”,函数返回这个对象本身。
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false
// 如果被 markRaw 标记了,即使在响应式对象中作属性,也依然不是响应式的
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false
就是给这个对象添加一个 ReactiveFlags.SKIP
标志位,这个逻辑写在了 getTargetType
这个函数里:
export function markRaw<T extends object>(value: T): T {
def(value, ReactiveFlags.SKIP, true)
return value
}
export const def = (obj: object, key: string | symbol, value: any) => {
Object.defineProperty(obj, key, {
configurable: true,
enumerable: false,
value
})
}