Reactive
# 目录
# Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
const p = new Proxy(target, handler)
- target: 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
- handler: 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
在 vue2.0 中使用 Object.defineProperty() 来劫持、监听对象属性,在 vue 3 中则采用了 Proxy 来劫持、监听对象属性,相比于 Object.defineProperty(),Proxy Api 的好处是:
- 可以监听到对象属性的增加和删除行为。
- 可以监听数组索引值或者长度的变化。
- 对 Map、Set、WeakMap 和 WeakSet 的支持。(Proxy 可以代理所有的对象)
Proxy Api 不兼容 ie 浏览器,这也意味着 vue3 是面向现代主流浏览器的,不支持 ie 浏览器,如果需要兼容 ie 浏览器,需要使用 vue2。
详见:MDN: Proxy (opens new window)
# Object.defineProperty
Object.defineProperty () 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty(obj, prop, descriptor)
- obj:要定义属性的对象。
- prop:要定义或修改的属性的名称或 Symbol 。
- descriptor:要定义或修改的属性描述符。
descriptor | 描述 | 类型 | 默认值 |
---|---|---|---|
configurable | 当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 | boolean | false |
enumerable | 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。 | boolean | false |
value | 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 | \ | undefined |
writable | 当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符 (en-US) 改变。 | boolean | false |
get | 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的 this 并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 | \ | undefined |
set | 属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。 | \ | undefined |
详见:MDN: Object.defineProperty() (opens new window)
# createReactiveObject
先来看一个最为重要的函数,这是实现 reactive/shallowReactive/readonly/shallowReadonly 这四个 api 的核心。
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
RAW = '__v_raw'
}
export interface Target {
[ReactiveFlags.SKIP]?: boolean // 表示当前当前监听的对象是否可以跳过(不监听)
[ReactiveFlags.IS_REACTIVE]?: boolean // 是否是 reactive 类型值
[ReactiveFlags.IS_READONLY]?: boolean // 是否是 readonly 类型值
[ReactiveFlags.RAW]?: any // 表示原始的值。
}
function createReactiveObject(
target: Target, // reactive target
isReadonly: boolean, // 是否只读
baseHandlers: ProxyHandler<any>, // 基本的 handlers,object/array
collectionHandlers: ProxyHandler<any>, // COLLECTION 类别的 handlers,map/set/weakMap/weakSet
proxyMap: WeakMap<Target, any> // 保存 Map(t, p) 集合
) {
// reactive 只作用于 object
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
// 已经是一个 Proxy 对象,返回其本身
// 如果原始值已经存在,但是又不是 readonly 值和 reactive 值。
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
// target 对应的 Proxy 对象已经存在于 proxyMap 中,返回这个 existingProxy。
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only a whitelist of value types can be observed.
// getTargetType 获取 target 的分类:INVALID、COMMON、COLLECTION
// 不支持的类型返回其本身。
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 根据 target 创建一个 Proxy 对象。
// COLLECTION 使用 collectionHandlers,否则使用 baseHandlers
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 将 proxy 对象加入到 proxyMap 中,结构: Map<target, proxy>。
proxyMap.set(target, proxy)
// 返回的是 proxy 对象,也就是说 target 的属性和行为将会被拦截,即是响应式。
return proxy
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
分析如下:
- 接收 5 个参数,target: 响应式的目标对象,也就是我们传入了需要响应式的值,isReadonly:是否只读,baseHandlers:基本的 handlers,用于 object/array,collectionHandlers:COLLECTION 类别的 handlers,适用于 map/set/weakMap/weakSet,proxyMap 保存 Map (target, proxy) 映射的 Map。
- 考虑到的特殊情况:不是 object,已经是一个 Proxy 对象,target 对应的 Proxy 对象已经存在于 proxyMap 中,不支持的类型。
- createReactiveObject 函数结构如下:
由此可见,handlers 和 proxyMap 将是很重要的部分。也是实现响应式 api 的核心。
# reactive
官网释义
Returns a reactive copy of the object.
The reactive conversion is "deep"—it affects all nested properties. In the ES2015 Proxy based implementation, the returned proxy is not equal to the original object. It is recommended to work exclusively with the reactive proxy and avoid relying on the original object.
返回对象的响应式副本。
响应式转换是 “深层的”:会影响对象内部所有嵌套的属性。基于 ES2015 的 Proxy 实现,返回的代理对象不等于原始对象。建议仅使用代理对象而避免依赖原始对象。
源码如下:
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
// readonly 值不可变,返回其本身
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: /*#__PURE__*/ createInstrumentationGetter(false, false)
}
export const reactiveMap = new WeakMap<Target, any>()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
handles 细节部分在 handler 文章中具体分析。
# readonly
官网释义
Takes an object (reactive or plain) or a ref and returns a readonly proxy to the original. A readonly proxy is deep: any nested property accessed will be readonly as well.
接受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property 也是只读的。
export function readonly<T extends object>(
target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
return createReactiveObject(
target,
true,
readonlyHandlers,
readonlyCollectionHandlers,
readonlyMap
)
}
export const readonlyHandlers: ProxyHandler<object> = {
get: readonlyGet,
set(target, key) {
return true
},
deleteProperty(target, key) {
return true
}
}
export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: /*#__PURE__*/ createInstrumentationGetter(true, false)
}
export const readonlyMap = new WeakMap<Target, any>()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- readonly 的对象的属性值是不可被 set 和 delete,均返回 true。
# shallowReactive
官网释义
Creates a reactive proxy that tracks reactivity of its own properties but does not perform deep reactive conversion of nested objects (exposes raw values).
创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (暴露原始值)。
export function shallowReactive<T extends object>(target: T): T {
return createReactiveObject(
target,
false,
shallowReactiveHandlers,
shallowCollectionHandlers,
shallowReactiveMap
)
}
export const shallowReactiveHandlers = /*#__PURE__*/ extend(
{},
mutableHandlers,
{
get: shallowGet,
set: shallowSet
}
)
export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: /*#__PURE__*/ createInstrumentationGetter(false, true)
}
export const shallowReactiveMap = new WeakMap<Target, any>()
export const extend = Object.assign
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# shallowReadonly
官网释义
Creates a proxy that makes its own properties readonly, but does not perform deep readonly conversion of nested objects (exposes raw values).
创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)。
export function shallowReadonly<T extends object>(
target: T
): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> {
return createReactiveObject(
target,
true,
shallowReadonlyHandlers,
shallowReadonlyCollectionHandlers,
shallowReadonlyMap
)
}
export const shallowReadonlyHandlers = /*#__PURE__*/ extend(
{},
readonlyHandlers,
{
get: shallowReadonlyGet
}
)
export const shallowReadonlyCollectionHandlers: ProxyHandler<
CollectionTypes
> = {
get: /*#__PURE__*/ createInstrumentationGetter(true, true)
}
export const shallowReadonlyMap = new WeakMap<Target, any>()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# isReadonly
官网释义
Checks if an object is a readonly proxy created by readonly.
检查对象是否是由 readonly 创建的只读代理。
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
2
3
isReadonly 是通过检查 target 的 __v_isReadonly
标记实现的。
# isReactive
官网释义
Checks if an object is a reactive proxy created by reactive.
检查对象是否是由 reactive 创建的响应式代理。
export function isReactive(value: unknown): boolean {
if (isReadonly(value)) {
return isReactive((value as Target)[ReactiveFlags.RAW])
}
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}
2
3
4
5
6
如果 value 只读,检查原始值是否是 reactive 类型,否则检查 __v_isReactive
标记。
# isProxy
官网释义
Checks if an object is a proxy created by reactive or readonly.
检查对象是否是由 reactive 或 readonly 创建的 proxy。
export function isProxy(value: unknown): boolean {
return isReactive(value) || isReadonly(value)
}
2
3
检查是否是 reactive 类型或者 readonly 类型值。
# markRaw
官网释义
Marks an object so that it will never be converted to a proxy. Returns the object itself.
标记一个对象,使其永远不会转换为 proxy。返回对象本身。
export function markRaw<T extends object>(value: T): T {
// markRaw 的对象标记 '__v_skip' 为 true ,在执行副作用时跳过
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
})
}
2
3
4
5
6
7
8
9
10
11
12
13
markRaw 是根据 __v_skip 标记实现的,在消费 effect 的时候,有此标记的 target 会跳过消费 effect 的过程,因此 markRaw 的对象就不在具有响应式。
# toRaw
官网释义
Returns the raw, original object of a reactive or readonly proxy. This is an escape hatch that can be used to temporarily read without incurring proxy access/tracking overhead or write without triggering changes. It is not recommended to hold a persistent reference to the original object. Use with caution.
返回 reactive 或 readonly 代理的原始对象。这是一个 “逃生舱”,可用于临时读取数据而无需承担代理访问 / 跟踪的开销,也可用于写入数据而避免触发更改。不建议保留对原始对象的持久引用。请谨慎使用。
export function toRaw<T>(observed: T): T {
return (
(observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed
)
}
2
3
4
5
- 如果不是 proxy 对象就原样返回,否则就递归找到 raw object。需要注意的是这个针对响应式数据的一种 “逃生舱” 机制。
- markRaw 和 toRaw 经常被用作一种优化的手段:在不需要通知视图时,减少数据变化引起的视图更新的花销;在数据不会变化的情况下,可以跳过 Proxy,提高渲染性能。
# Q&A
- createReactiveObject 创建了哪几种响应式的类型?
- reactive:完全响应式的,即 deep reactivity。
- readonly:完全只读的,即 deep readonly。
- shallowReactive:浅层的响应式,即 shallow reactivity。
- shallowReactive:浅层的只读,即 shallow readonly。
- createReactiveObject 能够处理哪些类型的数据?
从源码中对 target 的分类:
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
}
}
// 获取 value 的类型
function getTargetType(value: Target) {
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value))
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
可以处理的数据类型包括如下两类:
- COMMON:Object、Array。
- COLLECTION:Map、Set、WeakMap、WeakSet。
另外,需要注意的是,如果对象有 configurable: false 与 writable: false 的属性,那该对象就无法被 proxy 代理。
# 文章小结
这篇文章时对 reactivity 模块中 reactive 的源码分析,分析了四种 reactive object 的创建过程。各种 reactive object 都是由 Proxy 创建,创建是提供了不同的 handlers 和 proxyMap,proxyMap 就是对创建的 proxy 对象进行的缓存,handlers 是 Proxy 机制的核心,是劫持、控制对象属性的重要部分,这部分我们在 Handler 中详细分析。