# 数据响应式系统
Vue3
新增了Composition API
组合式API
,能够通过较低级别的数据驱动视图和组件生命周期,将与同一个逻辑关注点相关的代码配置在一起。从而实现一种更自由形式的编写组件逻辑的方式。
Vue3
的核心逻辑是基于Composition API
的,对于Options API
采用兼容处理。
Composition API
实现响应式的关键API
为包装基本类型的ref
和引用类型的reactive
。
# reactive
仅对简单的对象的reactive
过程做考虑的话,代码如下:
const proxyMap = new WeakMap<Target, any>()
export function reactive(target: object) {
const proxy = new Proxy(target, handlers)
proxyMap.set(target, proxy)
return proxy
}
2
3
4
5
6
7
8
9
逻辑很简单,就是通过Proxy
返回目标的对象的代理,并将目标的对象和代理的映射关系缓存到一个proxyMap
中,其代理的handlers
内容如下:
const handlers = {
get(target: Target, key: string | symbol, receiver: object){
const res = Reflect.get(target, key, receiver)
track(target, TrackOpTypes.GET, key)
return res
},
set(target: object, key: string | symbol, value: unknown, receiver: object): boolean{
const oldValue = (target as any)[key]
const hadKey = hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value)
}
}
return result
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
我们先只关注基本的get
和set
ProxyHandler
,其逻辑也比较简单。其中get
ProxyHandler
除了调用默认的Reflect API
,就是调用了track
方法。类似的,set
ProxyHandler
除了调用默认的Reflect API
以外,根据目标对象target
中是否有要set
的key
,没有时调用了TriggerOpTypes.ADD
类型的trigger
方法,已经有key
,并且值发生了改变时,调用了TriggerOpTypes.SET
类型的trigger
方法。
# 依赖收集与触发
Vue3
中的依赖以副作用函数effect
的方式体现。副作用函数就是指会产生副作用的函数,就是该函数的执行会直接或者间接影响其他的函数的执行,例如更新DOM
,修改作用域以外的变量等。
track
和trigger
主要负责依赖的收集(追踪)和触发,类似于Vue2
中的Dep
,分别在响应式数据get
和set
操作中被执行。用于将响应式数据和effect
关联起来。
这两个方法位于@vue/reactivity/src/effect.ts
文件中,在解析这两个方法之前,需要先对这两个方法依赖的几个局部变量做下说明:
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()
let activeEffect: ReactiveEffect | undefined
let shouldTrack = true
const trackStack: boolean[] = []
2
3
4
5
6
7
targetMap
是一个WeakMap
,其key
目标对象target
,其value
是一个以目标对象target
的key
为key
、其依赖的集合为value
的Map
,所以targetMap
是一个所有目标对象target
的依赖集合的映射总集合。
activeEffect
为当前激活状态的effect
,具体在effect分析
shouldTrack
用于表示是否需要开始依赖收集,trackStack
用于标识当前依赖收集的深度。主要用于effect
方法中。
# track
精简后代码如下:
export function track(target: Target, type: TrackOpTypes, key: unknown) {
if (!shouldTrack || activeEffect === undefined) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
从上面代码可以知道,track
的逻辑非常简单,就是向targetMap
的具体dep
中加入当前activeEffect
,并像activeEffect
的deps
中加入对于的dep
。
# trigger
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
// 需要触发的effect集合,通过add向其中添加内容
const effects = new Set<ReactiveEffect>()
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
// 不同的情况下,出发depsMap里的不同内容
if (type === TriggerOpTypes.CLEAR) {
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
if (key !== void 0) {
add(depsMap.get(key))
}
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
// 触发effect
const run = (effect: ReactiveEffect) => {
// 异步更新队列
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else { // 立即更新
effect()
}
}
effects.forEach(run)
}
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
trigger
的主要逻辑为在targetMap
中去除对应的depsMap
,然后根据不同的数据类型,不同的TriggerOpTypes
,触发对应的effect
。更新方式有异步更新队列和立即更新两种。
# effect
effect
方法返回ReactiveEffect
函数,作用相当于vue2
中的观察者Watcher
。简化后代码如下:
export function pauseTracking() {
trackStack.push(shouldTrack)
shouldTrack = false
}
export function enableTracking() {
trackStack.push(shouldTrack)
shouldTrack = true
}
export function resetTracking() {
const last = trackStack.pop()
shouldTrack = last === undefined ? true : last
}
const effectStack: ReactiveEffect[] = []
let uid = 0
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
if (!effect.active) {
return options.scheduler ? undefined : fn()
}
if (!effectStack.includes(effect)) {
cleanup(effect)
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = [] // 与该副作用函数存在联系的依赖的集合,在该effect执行时,会先将对应的deps清空,即cleanup函数的作用,以便effect执行后,在track过程中重新建立联系,从而避免副作用函数产生遗留
effect.options = options
return effect
}
function cleanup(effect: ReactiveEffect) {
const { deps } = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}
export function isEffect(fn: any): fn is ReactiveEffect {
return fn && fn._isEffect === true
}
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# 异步更新队列
# 原来的分析
reactive
方法源码位于@vue/reactivity/src/reactive.ts
的第63行,其逻辑很简单,先判断是否为readonly
,如果是,直接返回target
,否则调用createReactiveObject
方法:
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
)
}
2
3
4
5
6
7
8
9
10
11
12
createReactiveObject
方法位于同文件的第136行:
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
// 非对象,直接返回
if (!isObject(target)) return target
// 已经是一个Reactive Proxy,但不是Reactive Readonly Proxy直接返回
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) return target
// 已经在proxyMap中有对应的Reactive Proxy,直接返回existingProxy
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 不是禁止Reactive的类型
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 创建Reactive Proxy
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 在proxyMap中设置缓存
proxyMap.set(target, proxy)
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
在new Proxy
,根据TargetType
分别使用了collectionHandlers
和baseHandlers
,先看下baseHandlers
其值由reactive
调用时传入的实参mutableHandlers
,其定义位于@vue/reactivity/src/baseHandlers.ts
的第187行:
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
2
3
4
5
6
7
接下来我们一次看下对应的ProxyHandler
:
# get
mutableHandlers
的get``ProxyHandler
定义位于同文件的第35行,调用了同文件的第72行的createGetter
。
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 &&
receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
) {
return target
}
const targetIsArray = isArray(target)
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
const keyIsSymbol = isSymbol(key)
if (
keyIsSymbol
? builtInSymbols.has(key as symbol)
: key === `__proto__` || key === `__v_isRef`
) {
return res
}
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
if (shallow) {
return res
}
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
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
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
其主要逻辑为调用@vue/reactivity/src/effect.ts
的第141行的track
方法,并且根据类型对值进行如下的特殊处理:将ref
执行unwrap
,引用类型递归转为proxy
。
track
方法主要作用是追踪响应,将需要被追踪的对象作为键更新到全局的depsMap
里,并与activeEffect
关联起来,相当于Vue2
中的Dep
。
const targetMap = new WeakMap<any, KeyToDepMap>()
let activeEffect: ReactiveEffect | undefined
let shouldTrack = true
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (!shouldTrack || activeEffect === undefined) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# set
mutableHandlers
的set
ProxyHandler
定义位于同文件的第125行,调用了同文件的第128行的createGetter
。
const set = /*#__PURE__*/ createSetter()
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
const oldValue = (target as any)[key]
if (!shallow) {
value = toRaw(value)
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
}
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) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
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
当target
为引用类型是set
ProxyHandler
会根据是否hadKey
分别调用TriggerOpTypes
类型为SET
/ADD
类型的trigger
。
trigger
代码位于@vue/reactivity/src/effect.ts
的第167行,是响应的触发器,
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
const effects = new Set<ReactiveEffect>()
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
add(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
const run = (effect: ReactiveEffect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
}
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80