基本用法

动态组件就是让多个组件使用同一个挂载点,并动态切换。

在挂载点使用component标签,然后使用v-bind:is="组件",通过is切换组件。

1
2
3
4
5
6
7
8
<template>
<component :is="A"></component>
</template>

<script>
import A from "./A.vue"
import B from "./B.vue"
</script>

App.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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<template>
<div style="display: flex;">
<div
@click="switchCom(item, index)"
:class="[active == index? 'active': '']" class="tabs"
v-for="(item, index) in data" :key="index">
<div>{{ item.name }}</div>
</div>
</div>
<component :is="comId"></component>
</template>

<script setup lang="ts">
// 传组件对象的用法
import { ref, reactive } from 'vue'
import A from "@/components/A.vue"
import B from "@/components/B.vue"
import C from "@/components/C.vue"

const comId = ref(A)
const active = ref(0)

const data = reactive([
{
name: "A组件",
com: A
},
{
name: "B组件",
com: B
},
{
name: "C组件",
com: C
},
])

const switchCom = (item: any, index: number)=> {
comId.value = item.com
active.value = index
}
</script>

<script lang="ts">
// 另一种用法(传字符串),注册组件后就可以直接通过组件名切换
import AVue from "@/components/A.vue"
import BVue from "@/components/B.vue"
import CVue from "@/components/C.vue"

export default {
components: {
AVue,BVue,CVue
}
}

const comId2 = shallowRef("AVue")
const active2 = ref(0)

const data2 = reactive([
{
name: "A组件",
com: "AVue"
},
{
name: "B组件",
com: "BVue"
},
{
name: "C组件",
com: "CVue"
},
])

const switchCom2 = (item: any, index: number)=> {
comId2.value = item.com
active2.value = index
}
</script>

<style lang="scss">
.active {
background-color: #ccc;
}

.tabs {
border: 1px solid #ccc;
padding: 5px 10px;
margin: 5px;
cursor: pointer;
}
</style>

A.vue、B.vue和C.vue省略。

性能调优

引入组件时会包含组件自身的一些信息,但这些信息没有必要被劫持,为了性能起见可以跳过它们。

可以使用shallowRef或markRaw。

App.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
<template>
<div style="display: flex;">
<div
@click="switchCom(item, index)"
:class="[active == index? 'active': '']" class="tabs"
v-for="(item, index) in data" :key="index">
<div>{{ item.name }}</div>
</div>
</div>
<component :is="comId"></component>
</template>

<script setup lang="ts">
import { ref, reactive, markRaw, shallowRef } from 'vue'
import A from "@/components/A.vue"
import B from "@/components/B.vue"
import C from "@/components/C.vue"

// 使comId响应式只达到.value这一层
const comId = shallowRef(A)
const active = ref(0)

// 使这些组件对象变为不可被代理
const data = reactive([
{
name: "A组件",
com: markRaw(A)
},
{
name: "B组件",
com: markRaw(B)
},
{
name: "C组件",
com: markRaw(C)
},
])

const switchCom = (item: any, index: number)=> {
comId.value = item.com
active.value = index
}
</script>

<style lang="scss">
.active {
background-color: #ccc;
}

.tabs {
border: 1px solid #ccc;
padding: 5px 10px;
margin: 5px;
cursor: pointer;
}
</style>

源码

由图可知,使用component时,虚拟DOM调用了_resolveDynamicComponent函数。(工具

在Vue源码(/package/runtime-core/src/helpers/resolveAssets.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
// is属性就是此处的component参数
export function resolveDynamicComponent(component: unknown): VNodeTypes {
if (isString(component)) {
// 如果是字符串,作处理
return resolveAsset(COMPONENTS, component, false) || component
} else {
// invalid types will fallthrough to createVNode and raise warning
// 如果是对象,直接返回,然后调用render,重新patch,重新创建组件
return (component || NULL_DYNAMIC_COMPONENT) as any
}
}


function resolveAsset(
type: typeof COMPONENTS,
name: string,
warnMissing?: boolean,
maybeSelfReference?: boolean
): ConcreteComponent | undefined
// overload 2: directives
function resolveAsset(
type: typeof DIRECTIVES,
name: string
): Directive | undefined
// implementation
// overload 3: filters (compat only)
function resolveAsset(type: typeof FILTERS, name: string): Function | undefined
// implementation
function resolveAsset(
type: AssetTypes,
name: string,
warnMissing = true,
maybeSelfReference = false
) {
const instance = currentRenderingInstance || currentInstance // 获取组件实例
if (instance) {
// 读取组件实例上的type(代码书写方式)
const Component = instance.type

// explicit self name has highest priority
if (type === COMPONENTS) {
const selfName = getComponentName(
Component,
false /* do not include inferred name to avoid breaking existing code */
)
if (
selfName &&
(selfName === name ||
selfName === camelize(name) ||
selfName === capitalize(camelize(name)))
) {
return Component
}
}

// res是当前要切换的组件
const res =
// local registration
// check instance[type] first which is resolved for options API
// 局部注册
resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
// global registration
// 全局注册
resolve(instance.appContext[type], name)

if (!res && maybeSelfReference) {
// fallback to implicit self-reference
return Component
}

if (__DEV__ && warnMissing && !res) {
const extra =
type === COMPONENTS
? `\nIf this is a native custom element, make sure to exclude it from ` +
`component resolution via compilerOptions.isCustomElement.`
: ``
warn(`Failed to resolve ${type.slice(0, -1)}: ${name}${extra}`)
}

// 返回组件
return res
} else if (__DEV__) {
warn(
`resolve${capitalize(type.slice(0, -1))} ` +
`can only be used in render() or setup().`
)
}
}