effectreactive 的核心功能实现,它接收数据变化后的执行函数,官方称为副作用函数

ReactiveEffect 称为副作用对象

effect我个人认为不可以直接单独说,它与另外一个类息息相关,你中有我,我中有你,它就是-dep

首先我们先看下他们的关系

ReactiveEffect 和 Dep关系与类型定义

代码是他们的TS类型定义代码

1
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
// 首先看Dep的定义
// 它是一个Set,内部元素是 ReactiveEffect 类型,同时还有w和n两个属性
// & 表示类型合并
export type Dep = Set<ReactiveEffect> & TrackedMarkers

/**
* wasTracked and newTracked maintain the status for several levels of effect
* tracking recursion. One bit per level is used to define whether the dependency
* was/is tracked.
*/
type TrackedMarkers = {
/**
* wasTracked
* 标记是否在当前递归深度被收集过
*/
w: number
/**
* newTracked
* 标记在当前递归深度是否是重新收集
*/
n: number
}

// 在看ReactiveEffect
export class ReactiveEffect<T = any> {
// 别的代码

// 这里记录了当前副作用对象
deps: Dep[] = []

// 别的代码
}

从类型定义上来看,可以看出。他们两个的关系非常密切。

effect 函数接受一个副作用函数,生成一个副作用对象,也就是说,当响应式数据发生变化时,这个副作用函数会被调用,从而更新相关的视图。
而 dep 则是一个依赖追踪系统,用于追踪响应式数据被哪些副作用对象所依赖。在 effect 函数内部,当响应式数据被访问时,dep 会记录当前处于激活的副作用对象,并将这个副作用对象加入到依赖列表中。当响应式数据被修改时,dep 会遍历依赖列表,将所有依赖中的副作用对象上的副作用函数全部重新执行一遍,从而更新相关的视图。
因此,可以说 effect 和 dep 是密不可分的。effect 创建的副作用对象会依赖某些响应式数据,而这些响应式数据被修改时,就需要通过 dep 来通知调用相关的副作用对象上的副作用函数进行更新。effect 和 dep 的配合使用,是 Vue3 实现响应式更新的重要手段。

整体代码

ReactiveEffect的逻辑

都在代码里面了

1
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221

export interface DebuggerOptions {
onTrack?: (event: DebuggerEvent) => void // 追踪时触发
onTrigger?: (event: DebuggerEvent) => void // 触发回调时触发
}

export interface ReactiveEffectOptions extends DebuggerOptions {
lazy?: boolean // 是否延迟触发 effect
scheduler?: EffectScheduler // 调度者
scope?: EffectScope //
allowRecurse?: boolean //
onStop?: () => void // 停止监听时触发
}

export interface ReactiveEffectRunner<T = any> {
(): T
effect: ReactiveEffect
}

// The main WeakMap that stores {target -> key -> dep} connections.
// Conceptually, it's easier to think of a dependency as a Dep class
// which maintains a Set of subscribers, but we simply store them as
// raw Sets to reduce memory overhead.
type KeyToDepMap = Map<any, Dep>

// 内存中的数据结构
// {obj}: Map<any, Set<ReactiveEffect>>
// 存储收集的依赖,使用WeakMap弱引用,当key失去引用时,可以被垃圾回收机制回收。
const targetMap = new WeakMap<any, KeyToDepMap>()

// The number of effects currently being tracked recursively.
// 当前递归跟踪的层数。
let effectTrackDepth = 0

// 当前是第几层的递归
export let trackOpBit = 1

/**
* The bitwise track markers support at most 30 levels of recursion.
* This value is chosen to enable modern JS engines to use a SMI on all platforms.
* When recursion depth is greater, fall back to using a full cleanup.
*/
/**
* 按位跟踪标记最多支持 30 级递归。为什么只能30层,在下面有说
* 选择此值是为了使现代 JS 引擎能够在所有平台上使用 SMI。
* 当递归深度更大时,退回到使用完全清理。
*/
const maxMarkerBits = 30

/**
* @param fn 副作用函数
* @param options 选项
* @return 返回一个 runner,是一个函数,直接调用就是调用副作用函数;runner 的属性 effect,保存着它对应的 ReactiveEffect 对象。
* */ */
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
// 如果已经是effect的话,重新初始化为原来的函数
if ((fn as ReactiveEffectRunner).effect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
// 实例化一个 ReactiveEffect
const _effect = new ReactiveEffect(fn)
if (options) {
// 合并配置项
extend(_effect, options)
// 可以暂时不看,与 effectScope API 相关 https://v3.cn.vuejs.org/api/effect-scope.html#effectscope
// 将当前 ReactiveEffect 副作用对象,记录到 effectScope 中
// 当 effectScope.stop() 被调用时,所有的 ReactiveEffect 对象都会被 stop
if (options.scope) recordEffectScope(_effect, options.scope)
}
// 没有延迟的话,立即执行一次 effect
if (!options || !options.lazy) {
_effect.run()
}
// 绑定副作用函数的this对象和effect对象
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}


export class ReactiveEffect<T = any> {
// 是否处于激活
active = true
// 当前 effect 的dep 数组
deps: Dep[] = []
// 父级 effect
parent: ReactiveEffect | undefined = undefined

/**
* Can be attached after creation
* 创建后可以附加
* @internal
*/
computed?: ComputedRefImpl<T> // 标记为computed
/**
* @internal
*/
allowRecurse?: boolean // 允许 递归调用
/**
* @internal 这个单词表示内部的意思
*/
private deferStop?: boolean // 延迟停止正执行的effect

onStop?: () => void
// dev only
onTrack?: (event: DebuggerEvent) => void
// dev only
onTrigger?: (event: DebuggerEvent) => void

constructor(
public fn: () => T, // 回调函数
public scheduler: EffectScheduler | null = null, // 调度器
scope?: EffectScope // 作用域
) {
// 记录 该副作用所属的作用域
recordEffectScope(this, scope)
}

run() {
// active 为false 说明响应副作用已经被停止
if (!this.active) {
// 直接调用并返回 传入的fn 的执行结果
return this.fn()
}
// parent 赋值 activeEffect:正在执行的副作用
let parent: ReactiveEffect | undefined = activeEffect
// shouldTrack 为全局的变量:当前副作用是否需要被追踪
let lastShouldTrack = shouldTrack
// 遍历 parent
while (parent) {
// 当前副作用 已在父副作用链 中,存在循环依赖
if (parent === this) {
// 确保不会在当前作用域中递归执行同一个副作用函数
return
}
parent = parent.parent
}
try {
this.parent = activeEffect
activeEffect = this
shouldTrack = true
// 这里是一个优化算法 文章参考来源: https://juejin.cn/post/7048970987500470279#heading-11
// effectTrackDepth 表示当前副作用嵌套深度。
// 计算过程是先转二进制,然后在最右边加个 0。
// 1 << 1 结果 10 对应10进制 2
// 1 << 2 结果 100 对应10进制 4
// 1 << 3 结果 100 对应10进制 8
// 使用 << 运算符,相当于乘 2。
// 1 << 1 === 1 * 2
// 1 << 2 === 1 * 2* 2
// 5 << 1 === 5 * 2
// 5 << 2 === 5 * 2 * 2
// 这里是通过 000001来标记当前是第几层 深度最大是30层
// 为什么最大标记嵌套深度为 30?
// 深度受存储类型的位数限制,否则就会溢出。
// 在JavaScript内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,
// 是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数。
// 1 << 30 => 1073741824
// 1 << 31 => -2147483648,溢出
// 深度最大为 30,超过 30,则需要降级方案,使用全部清除再全部重新收集依赖的方案
trackOpBit = 1 << ++effectTrackDepth

if (effectTrackDepth <= maxMarkerBits) {
// 初始化依赖标记,用于检测依赖是否变化。
initDepMarkers(this)
} else {
// 清除 依赖关系
cleanupEffect(this)
}
return this.fn()
} finally {
// 最后的依赖关系处理
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}

trackOpBit = 1 << --effectTrackDepth

activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
// 延迟停止正执行的 effect
if (this.deferStop) {
this.stop()
}
}
}

stop() {
// stopped while running itself - defer the cleanup
if (activeEffect === this) {
// 该 effect 正执行, deferStop标记为true
this.deferStop = true
} else if (this.active) {
// 清理该effect
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
// 最后将 激活状态设置为false
this.active = false
}
}
}
/**
* @info 清理指定的副作用对象
* 根据副作用对象上的deps记录的dep中清除它
* */
function cleanupEffect(effect: ReactiveEffect) {
const {deps} = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}

其中dep 收集当前副作用对象时,涉及到一些dep的操作函数在这里

1
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
// 判断是否以前被收集过
export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0

// & 对一对数位执行位运算 & 时,如果数位均为 1 则返回 1。、
// 0 & 0 => 0
// 0 & 1 => 0
// 1 & 1 => 1
// 1111 & 0000 => 0000
// 1111 & 0001 => 0001
// 判断是否是重新收集
export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0

// 初始化副作用所绑定的每个dep 的 w 标记
export const initDepMarkers = ({ deps }: ReactiveEffect) => {
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
// 对一对数位执行位运算 | 时,如果其中一位是 1 则返回 1。
// 0 | 0 => 0
// 1 | 0 => 1
// 1 | 1 => 1
// 0000 | 1111 => 1111
// 0011 | 0000 => 0011
// trackOpBit 是一个全局变量,根据当前深度生成的
// 如果 当前的w是 10 但是 trackOpBit是100 两个数据|运算后得到 000 表示在当前深度2的时候这个dep并没有被标记
deps[i].w |= trackOpBit // set was tracked 设置被跟踪
}
}
}
// 对当前副作用所有绑定的dep,进行对当前副作用的判断清理,以及标记回归
export const finalizeDepMarkers = (effect: ReactiveEffect) => {
const { deps } = effect
if (deps.length) {
let ptr = 0
for (let i = 0; i < deps.length; i++) {
const dep = deps[i]
// 对失效依赖进行删除(有 was 但是没有 new)
if (wasTracked(dep) && !newTracked(dep)) {
dep.delete(effect)
} else {
// 需要保留的依赖,放到数据的较前位置,因为在最后会删除较后位置的所有依赖
deps[ptr++] = dep
}
// clear bits
// 清理 was 和 new 标记,将它们对应深度的 bit,置为 0
dep.w &= ~trackOpBit
dep.n &= ~trackOpBit
}
// 删除依赖,只保留需要的
deps.length = ptr
}
}

依赖收集

通过前面的baseHanlder 可以知道收集依赖是在访问当前属性时触发函数 track进行收集,代码实现如下:

1
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
// 全局变量
// 内存中的数据结构
// {obj}: Map<any, Set<ReactiveEffect>>
// 存储收集的依赖,使用WeakMap弱引用,当key失去引用时,可以被垃圾回收机制回收。
const targetMap = new WeakMap<any, KeyToDepMap>()

/**
* @info 收集依赖
* @param target 当前被监听的数据对象
* @param type 内部的处理分支
* @param key 当前被监听的数据对象的属性name
* */
export function track(target: object, type: TrackOpTypes, key: unknown) {
// 是否需要收集以及当前有没有处于激活的effect
if (shouldTrack && activeEffect) {
// 查找下这个对象有么有别的依赖
let depsMap = targetMap.get(target)
if (!depsMap) {
// 没有的话当作第一个依赖进行收集
targetMap.set(target, (depsMap = new Map()))
}
// 看下当前这个key有没有别的依赖
let dep = depsMap.get(key)
// 没有当作第一个依赖
if (!dep) {
depsMap.set(key, (dep = createDep()))
}

// 调试模式
const eventInfo = __DEV__
? {effect: activeEffect, target, type, key}
: undefined

trackEffects(dep, eventInfo)
}
}

/**
*
* */
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
let shouldTrack = false
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
dep.n |= trackOpBit // set newly tracked 设置新跟踪
shouldTrack = !wasTracked(dep) // 看下是否被收集过了
}
} else {
// Full cleanup mode.
shouldTrack = !dep.has(activeEffect!)
}

if (shouldTrack) {
dep.add(activeEffect!) // 添加进dep中
activeEffect!.deps.push(dep) // 添加进副作用对象中
if (__DEV__ && activeEffect!.onTrack) {
activeEffect!.onTrack({
effect: activeEffect!,
...debuggerEventExtraInfo!
})
}
}
}

依赖触发

代码

1
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/**
* @info 收集依赖
* @param target 当前被监听的数据对象
* @param type 内部的处理分支
* @param key 当前被监听的数据对象的属性name
* */
export function track(target: object, type: TrackOpTypes, key: unknown) {
// 是否需要收集以及当前有没有处于激活的effect
if (shouldTrack && activeEffect) {
// 查找下这个对象有么有别的依赖
let depsMap = targetMap.get(target)
if (!depsMap) {
// 没有的话当作第一个依赖进行收集
targetMap.set(target, (depsMap = new Map()))
}
// 看下当前这个key有没有别的依赖
let dep = depsMap.get(key)
// 没有当作第一个依赖
if (!dep) {
depsMap.set(key, (dep = createDep()))
}

// 调试模式
const eventInfo = __DEV__
? {effect: activeEffect, target, type, key}
: undefined

trackEffects(dep, eventInfo)
}
}

/**
*
* */
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
let shouldTrack = false
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
dep.n |= trackOpBit // set newly tracked 设置新跟踪
shouldTrack = !wasTracked(dep) // 这里应该是看下是不是一个新的依赖?
}
} else {
// Full cleanup mode.
shouldTrack = !dep.has(activeEffect!)
}

if (shouldTrack) {
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
if (__DEV__ && activeEffect!.onTrack) {
activeEffect!.onTrack({
effect: activeEffect!,
...debuggerEventExtraInfo!
})
}
}
}

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
}

let deps: (Dep | undefined)[] = []

// 这中间应该是对一个数据的API操作的响应目前没看明白 比如Set或Map的clear方法
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
// 集合被清除
// 触发目标的所有效果
deps = [...depsMap.values()]
} else if (key === 'length' && isArray(target)) {
const newLength = Number(newValue)
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= newLength) {
deps.push(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
deps.push(depsMap.get(key))
}

// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
deps.push(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY))
}
break
}
}
// 这中间应该是对一个数据的API操作的响应目前没看明白

const eventInfo = __DEV__
? {target, type, key, newValue, oldValue, oldTarget}
: undefined

// 下面就是触发副作用对象的副作用函数
if (deps.length === 1) {
if (deps[0]) {
if (__DEV__) {
triggerEffects(deps[0], eventInfo)
} else {
triggerEffects(deps[0])
}
}
} else {
const effects: ReactiveEffect[] = []
for (const dep of deps) {
if (dep) {
effects.push(...dep)
}
}
if (__DEV__) {
triggerEffects(createDep(effects), eventInfo)
} else {
triggerEffects(createDep(effects))
}
}
}

export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// spread into array for stabilization
const effects = isArray(dep) ? dep : [...dep]
// 循环遍历 dep,去取每个依赖的副作用对象 ReactiveEffect
for (const effect of effects) {
if (effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo)
}
}
for (const effect of effects) {
if (!effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo)
}
}
}

function triggerEffect(
effect: ReactiveEffect,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// 默认不允许递归,即当前 effect 副作用函数,如果递归触发当前 effect,会被忽略
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({effect}, debuggerEventExtraInfo))
}
// effect.scheduler可以先不管,ref 和 reactive 都没有
if (effect.scheduler) {
effect.scheduler()
} else {
// 执行 effect 的副作用函数
effect.run()
}
}
}