组件

每一个.vue文件都可以充当组件使用,每一个组件都可以复用。

App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
<h1>1145141919810</h1>
<!-- 组件可以进行复用,还可以做循环、嵌套、传参 -->
<A></A>
<A></A>
<A></A>
</div>
</template>

<script setup lang="ts">
import { ref, reactive } from "vue"

// 不能和html标签名起冲突
// import div from "@/components/A.vue"
import A from "@/components/A.vue"

// 在Vue2中需要通过components选项对组件进行注册,但Vue3中不需要(开箱即用)
// component: {
// A
// }
</script>

A.vue:

1
2
3
4
5
6
<template>
<div>
<p>我是一个组件</p>
<hr>
</div>
</template>

Vue3生命周期

一个组件从创建到销毁的全过程。

App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>
<h1>1145141919810</h1>
<!-- 当组件绑定了v-if,flag值变化时会触发组件的创建或销毁,而v-show不会 -->
<A v-if="flag"></A>
<A v-if="flag"></A>
<button @click="flag=!flag">创建/销毁</button>
</div>
</template>

<script setup lang="ts">
import { ref, reactive } from "vue"
import A from "@/components/A.vue"
const flag = ref<Boolean>(false)
</script>

A.vue:

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
<template>
<div>
<p>我是一个组件</p>
<div ref="div">{{ str }}</div>
<button @click="change">修改str</button>
<hr>
</div>
</template>

<script setup lang="ts">
// 在setup语法糖模式中没有beforeCreate和created(因为相当于setup)
import { getCurrentInstance, ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onRenderTracked, onRenderTriggered } from "vue"

const str = ref<string>("野兽先辈")
const div = ref<HTMLDivElement>()
const instance = getCurrentInstance()

const change = ()=> {
str.value = str.value + 1
}

// 在setup时还读不到任何DOM元素
// 可以看到这些生命周期函数都被注册到组件实例上了
console.log("setup", div.value, instance)

// 挂载前
onBeforeMount(()=> {
// 在onBeforeMount时还读不到任何DOM元素
console.log("onBeforeMount", div.value)
})

// 挂载后
onMounted(()=> {
// 从onMounted开始能读到DOM元素了
console.log("onMounted", div.value)
})

// 更新前
onBeforeUpdate(()=> {
// 在onBeforeUpdate时DOM元素还未被更新
console.log("onBeforeUpdate", div.value?.innerText)
})

// 更新后
onUpdated(()=> {
// 从onUpdated开始DOM元素已被更新了
console.log("onUpdated", div.value?.innerText)
})

// 销毁前
onBeforeUnmount(()=> {
// 在onBeforeUnmount时还能读到DOM元素
console.log("onBeforeUnmount", div.value)
})

// 销毁后
onUnmounted(()=> {
// 从onUnmounted开始组件已经被销毁了,自然读不到DOM元素了
console.log("onUnmounted", div.value)
})

// 收集依赖时
onRenderTracked((e)=> {
// 用来调试代码,e其实就是一个effect对象
console.log("onRenderTracked", e)
})

// 更新依赖时
onRenderTriggered((e)=> {
// 用来调试代码,e其实就是一个effect对象
console.log("onRenderTriggered", e)
})
</script>

源码

在Vue源码(/package/runtime-core/src/apiLifecycle.ts)中可以看到watch的源码。

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
// 在这个函数里创建钩子并作缓存
export function injectHook(
type: LifecycleHooks,
hook: Function & { __weh?: Function },
target: ComponentInternalInstance | null = currentInstance,
prepend: boolean = false
): Function | undefined {
if (target) {
// 如果有钩子函数直接返回,否则创建一个空数组(Function类型数组)
const hooks = target[type] || (target[type] = [])
// cache the error handling wrapper for injected hooks so the same hook
// can be properly deduped by the scheduler. "__weh" stands for "with error
// handling".
const wrappedHook =
hook.__weh ||
(hook.__weh = (...args: unknown[]) => {
// 如果组件卸载了,什么都不做
if (target.isUnmounted) {
return
}
// disable tracking inside all lifecycle hooks
// since they can potentially be called inside effects.
// 停止依赖收集,避免重复收集依赖(在组件初始化时就收集过依赖了)
pauseTracking()
// Set currentInstance during hook invocation.
// This assumes the hook does not synchronously trigger other hooks, which
// can only be false when the user does something really funky.
// 设target为当前组件实例
setCurrentInstance(target)
// 执行钩子函数
const res = callWithAsyncErrorHandling(hook, target, type, args)
// 清空当前组件实例
unsetCurrentInstance()
// 恢复依赖收集
resetTracking()
return res
})
if (prepend) {
hooks.unshift(wrappedHook)
} else {
// 添加hook
hooks.push(wrappedHook)
}
return wrappedHook
} else if (__DEV__) {
const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, ''))
warn(
`${apiName} is called when there is no active component instance to be ` +
`associated with. ` +
`Lifecycle injection APIs can only be used during execution of setup().` +
(__FEATURE_SUSPENSE__
? ` If you are using async setup(), make sure to register lifecycle ` +
`hooks before the first await statement.`
: ``)
)
}
}

// 注册生命周期,使用函数柯里化封装,因为此处只是第一个参数不同,其他的都相同
export const createHook =
<T extends Function = () => any>(lifecycle: LifecycleHooks) =>
(hook: T, target: ComponentInternalInstance | null = currentInstance) =>
// post-create lifecycle registrations are noops during SSR (except for serverPrefetch)
(!isInSSRComponentSetup || lifecycle === LifecycleHooks.SERVER_PREFETCH) &&
injectHook(lifecycle, (...args: unknown[]) => hook(...args), target)

// 导出一些钩子函数,它们都调用了createHook函数,并传入一个参数(枚举)
// 因为生命周期钩子函数是在组件生命周期的各个阶段去执行的,所以把这些钩子放到组件实例上
// export const enum LifecycleHooks {
// BEFORE_CREATE = 'bc',
// CREATED = 'c',
// BEFORE_MOUNT = 'bm',
// MOUNTED = 'm',
// BEFORE_UPDATE = 'bu',
// UPDATED = 'u',
// BEFORE_UNMOUNT = 'bum',
// UNMOUNTED = 'um',
// DEACTIVATED = 'da',
// ACTIVATED = 'a',
// RENDER_TRIGGERED = 'rtg',
// RENDER_TRACKED = 'rtc',
// ERROR_CAPTURED = 'ec',
// SERVER_PREFETCH = 'sp'
// }
export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT)
export const onMounted = createHook(LifecycleHooks.MOUNTED)
export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE)
export const onUpdated = createHook(LifecycleHooks.UPDATED)
export const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT)
export const onUnmounted = createHook(LifecycleHooks.UNMOUNTED)
export const onServerPrefetch = createHook(LifecycleHooks.SERVER_PREFETCH)

钩子被注册后,在/package/runtime-core/src/render.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
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
// 挂载和更新
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
const componentUpdateFn = () => {
// 如果当前组件没有挂载
if (!instance.isMounted) {
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, parent } = instance
const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)

toggleRecurse(instance, false)
// beforeMount hook
// 执行beforeMount钩子
if (bm) {
invokeArrayFns(bm)
}
// onVnodeBeforeMount
if (
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeBeforeMount)
) {
invokeVNodeHook(vnodeHook, parent, initialVNode)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
instance.emit('hook:beforeMount')
}
toggleRecurse(instance, true)

// 执行完beforeMount钩子后进行渲染
if (el && hydrateNode) {
// vnode has adopted host node - perform hydration instead of mount.
const hydrateSubTree = () => {
if (__DEV__) {
startMeasure(instance, `render`)
}
instance.subTree = renderComponentRoot(instance)
if (__DEV__) {
endMeasure(instance, `render`)
}
if (__DEV__) {
startMeasure(instance, `hydrate`)
}
hydrateNode!(
el as Node,
instance.subTree,
instance,
parentSuspense,
null
)
if (__DEV__) {
endMeasure(instance, `hydrate`)
}
}

if (isAsyncWrapperVNode) {
;(initialVNode.type as ComponentOptions).__asyncLoader!().then(
// note: we are moving the render call into an async callback,
// which means it won't track dependencies - but it's ok because
// a server-rendered async wrapper is already in resolved state
// and it will never need to change.
() => !instance.isUnmounted && hydrateSubTree()
)
} else {
hydrateSubTree()
}
} else {
if (__DEV__) {
startMeasure(instance, `render`)
}
const subTree = (instance.subTree = renderComponentRoot(instance))
if (__DEV__) {
endMeasure(instance, `render`)
}
if (__DEV__) {
startMeasure(instance, `patch`)
}
// 挂载Vnode到容器中
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
if (__DEV__) {
endMeasure(instance, `patch`)
}
// el是DOM元素,此时已经可以读到它了
initialVNode.el = subTree.el
}
// mounted hook
// 执行mounted钩子
if (m) {
queuePostRenderEffect(m, parentSuspense)
}
// onVnodeMounted
if (
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeMounted)
) {
const scopedInitialVNode = initialVNode
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
parentSuspense
)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:mounted'),
parentSuspense
)
}

// activated hook for keep-alive roots.
// #1742 activated hook must be accessed after first render
// since the hook may be injected by a child keep-alive
if (
initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE ||
(parent &&
isAsyncWrapper(parent.vnode) &&
parent.vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE)
) {
instance.a && queuePostRenderEffect(instance.a, parentSuspense)
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:activated'),
parentSuspense
)
}
}
instance.isMounted = true

if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsComponentAdded(instance)
}

// #2458: deference mount-only object parameters to prevent memleaks
initialVNode = container = anchor = null as any
} else {
// updateComponent
// This is triggered by mutation of component's own state (next: null)
// OR parent calling processComponent (next: VNode)
let { next, bu, u, parent, vnode } = instance
let originNext = next
let vnodeHook: VNodeHook | null | undefined
if (__DEV__) {
pushWarningContext(next || instance.vnode)
}

// Disallow component effect recursion during pre-lifecycle hooks.
toggleRecurse(instance, false)
if (next) {
next.el = vnode.el
updateComponentPreRender(instance, next, optimized)
} else {
next = vnode
}

// beforeUpdate hook
// 执行beforeUpdate钩子
if (bu) {
invokeArrayFns(bu)
}
// onVnodeBeforeUpdate
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHook, parent, next, vnode)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
instance.emit('hook:beforeUpdate')
}
toggleRecurse(instance, true)

// render
// 执行beforeUpdate钩子后也要进行渲染
if (__DEV__) {
startMeasure(instance, `render`)
}
const nextTree = renderComponentRoot(instance)
if (__DEV__) {
endMeasure(instance, `render`)
}
const prevTree = instance.subTree
instance.subTree = nextTree

if (__DEV__) {
startMeasure(instance, `patch`)
}
patch(
prevTree,
nextTree,
// parent may have changed if it's in a teleport
hostParentNode(prevTree.el!)!,
// anchor may have changed if it's in a fragment
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
if (__DEV__) {
endMeasure(instance, `patch`)
}
next.el = nextTree.el
if (originNext === null) {
// self-triggered update. In case of HOC, update parent component
// vnode el. HOC is indicated by parent instance's subTree pointing
// to child component's vnode
updateHOCHostEl(instance, nextTree.el)
}
// updated hook
// 执行updated钩子
if (u) {
queuePostRenderEffect(u, parentSuspense)
}
// onVnodeUpdated
// 组件更新后执行,此时DOM已经更新
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, next!, vnode),
parentSuspense
)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:updated'),
parentSuspense
)
}

if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsComponentUpdated(instance)
}

if (__DEV__) {
popWarningContext()
}
}
}

// create reactive effect for rendering
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(update),
instance.scope // track it in component's effect scope
))

const update: SchedulerJob = (instance.update = () => effect.run())
update.id = instance.uid
// allowRecurse
// #1801, #2043 component render effects should allow recursive updates
toggleRecurse(instance, true)

if (__DEV__) {
effect.onTrack = instance.rtc
? e => invokeArrayFns(instance.rtc!, e)
: void 0
effect.onTrigger = instance.rtg
? e => invokeArrayFns(instance.rtg!, e)
: void 0
update.ownerInstance = instance
}

update()
}

// 卸载
const unmount: UnmountFn = (
vnode,
parentComponent,
parentSuspense,
doRemove = false,
optimized = false
) => {
const {
type,
props,
ref,
children,
dynamicChildren,
shapeFlag,
patchFlag,
dirs
} = vnode
// unset ref
if (ref != null) {
setRef(ref, null, parentSuspense, vnode, true)
}

if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)
return
}

const shouldInvokeDirs = shapeFlag & ShapeFlags.ELEMENT && dirs
const shouldInvokeVnodeHook = !isAsyncWrapper(vnode)

let vnodeHook: VNodeHook | undefined | null
if (
shouldInvokeVnodeHook &&
(vnodeHook = props && props.onVnodeBeforeUnmount)
) {
invokeVNodeHook(vnodeHook, parentComponent, vnode)
}

if (shapeFlag & ShapeFlags.COMPONENT) {
// 清除组件引用的effect副作用函数
unmountComponent(vnode.component!, parentSuspense, doRemove)
} else {
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
vnode.suspense!.unmount(parentSuspense, doRemove)
return
}

// 执行beforeUnmount钩子
if (shouldInvokeDirs) {
invokeDirectiveHook(vnode, null, parentComponent, 'beforeUnmount')
}

if (shapeFlag & ShapeFlags.TELEPORT) {
;(vnode.type as typeof TeleportImpl).remove(
vnode,
parentComponent,
parentSuspense,
optimized,
internals,
doRemove
)
} else if (
dynamicChildren &&
// #1153: fast path should not be taken for non-stable (v-for) fragments
(type !== Fragment ||
(patchFlag > 0 && patchFlag & PatchFlags.STABLE_FRAGMENT))
) {
// fast path for block nodes: only need to unmount dynamic children.
// 组件卸载完成后,清空当前组件下所有的子节点
unmountChildren(
dynamicChildren,
parentComponent,
parentSuspense,
false,
true
)
} else if (
(type === Fragment &&
patchFlag &
(PatchFlags.KEYED_FRAGMENT | PatchFlags.UNKEYED_FRAGMENT)) ||
(!optimized && shapeFlag & ShapeFlags.ARRAY_CHILDREN)
) {
unmountChildren(children as VNode[], parentComponent, parentSuspense)
}

if (doRemove) {
remove(vnode)
}
}

// 卸载完成,执行unmounted钩子
if (
(shouldInvokeVnodeHook &&
(vnodeHook = props && props.onVnodeUnmounted)) ||
shouldInvokeDirs
) {
queuePostRenderEffect(() => {
vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
shouldInvokeDirs &&
invokeDirectiveHook(vnode, null, parentComponent, 'unmounted')
}, parentSuspense)
}
}