3-8、Vue 组件高级应用和插件

组件高级应用

动态组件

官方文档:https://cn.vuejs.org/guide/essentials/component-basics.html#dynamic-components

基本语法

1
2
3
4
5
6
7
8
9
10
11
<template>
<div>
<!-- 将根据 comName 的值渲染对应的组件 -->
<component :is="comName" />
</div>
</template>

<script setup lang="ts">
import Content1 from './Content1'
const comName = Content1
</script>

使用场景

异步动态组件

官方文档:https://cn.vuejs.org/guide/components/async.html#async-components

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- <template> -->
<div>
<button @click="handleChange">切换</button>

<!-- 将根据 comName.component 的值渲染对应的组件 -->
<component :is="comName.component" />
</div>
</template>

<script setup lang="ts">
import { defineAsyncComponent } from 'vue'

const comName = reactive({
name: '',
component: ''
})

const handleChange = () => {
comName.name = comName.name === 'Content1' ? 'Content2' : 'Content1'

// defineAsyncComponent 异步加载,当需要的时候才进行加载
comName.component = defineAsyncComponent(() => import(`./${comName.name}.vue`))
}
</script>

动态组件切换时,生命周期也是正常的销毁、创建,不会保留。

KeepAlive

官方文档:https://cn.vuejs.org/guide/built-ins/keep-alive.html#keepalive
作用:在多个组件间动态切换时缓存被移除的组件实例,不会触发创建、销毁的生命周期。
配合动态组件使用更佳

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
<template>
<div>
<button @click="handleChange">切换</button>

<!-- 将根据 comName.component 的值渲染对应的组件 -->
<KeepAlive>
<component :is="comName.component" />
</KeepAlive>
</div>
</template>

<script setup lang="ts">
import { defineAsyncComponent } from 'vue'

const comName = reactive({
name: '',
component: ''
})

const handleChange = () => {
comName.name = comName.name === 'Content1' ? 'Content2' : 'Content1'

// defineAsyncComponent 异步加载,当需要的时候才进行加载
comName.component = defineAsyncComponent(() => import(`./${comName.name}.vue`))
}
</script>

由于配合defineAsyncComponent使用,切换时将不会销毁组件,但每次还是会“新”创建,即不会有“销毁”的生命周期。

其他内置组件

官方文档:https://cn.vuejs.org/api/built-in-components.html#built-in-components

Teleport - 传送门

官方文档:https://cn.vuejs.org/guide/built-ins/teleport.html
作用:将其插槽内容渲染到 DOM 中的另一个位置。

1
2
3
<teleport to="body">
<h1>我将显示在 body 元素内</h1>
</teleport>

Transition - 过渡态

官方文档:https://cn.vuejs.org/guide/built-ins/transition.html
作用:制作基于状态变化的过渡和动画

1
2
3
4
<button @click="show = !show">Toggle</button>
<Transition>
<p v-if="show">hello</p>
</Transition>
1
2
3
4
5
6
7
8
9
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
opacity: 0;
}

Suspense - 悬停

官方文档:https://cn.vuejs.org/guide/built-ins/suspense.html
作用:用来在组件树中协调对异步依赖的处理

插件

基本使用

1
2
3
4
5
6
7
8
9
10
11
// custom.js
export default {
install(app, options) {
console.log(app, options)

// 其他业务逻辑...
}
}

// main.js
app.use(customPlugin, { xx: 1 })

场景使用

  • 注册全局的组件
  • 注册全局的方法,通过挂载到 Vue 的原型链上实现
  • 注册全局的自定义指令
1
2
3
4
5
6
7
8
9
10
11
12
// custom.js
export default {
install(app, options) {
console.log(app, options)

// 其他业务逻辑...

// app.component('MyCom', MyCom) 注册组件
// app.config.globalProperties.$xx = xx 注册到原型链上
// app.directive('auth', (el, binding) => {}) 注册自定义指令
}
}

面试题

手写异步组件

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
import { type Component, ref } from 'vue'

// Vue 源码中,异步组件的返回值是 Promise<Component>
type AsyncComponentLoader = () => Promise<Component>

// Vue 源码中,异步组件的配置项
interface AsyncComponentOptions {
loader: AsyncComponentLoader
loadingComponent?: Component
errorComponent?: Component
delay?: number
timeout?: number
suspensible?: boolean
onError?: (error: Error, retry: () => void, fail: () => void, attempts: number) => any
}

/**
* 定义异步组件
*
* @param source 异步组件的加载器或配置对象
* @returns 返回一个包装后的组件
*/
export function defineAsyncComponent(
source: AsyncComponentLoader | AsyncComponentOptions
): Component {
// --------------- 以下为手写的代码 ---------------

// 如果source是一个函数,则将其视为loader
if (typeof source === 'function') {
source = { loader: source }
}

// 如果source没有loader属性,则抛出错误
if (!source.loader) {
throw new Error('Loader is required for async component')
}

// 提取loader、delay和timeout属性
const { loader, delay = 0, timeout = 0, loadingComponent, errorComponent } = source
let InnerComp: Component

return {
name: 'AsyncComponentWrapper',
setup() {
// 定义loaded、loading和error的响应式引用
const loaded = ref(false)
const loading = ref(false)
const error = ref<Error | null>(null) // 添加类型注解并初始化为null

// 定义loader函数,用于异步加载组件
const loaderFn = async () => {
// 标记为async以支持await语法
loading.value = true
try {
// 使用await等待加载完成,将加载完成的组件赋值给InnerComp
InnerComp = await loader() // 使用await等待加载完成
loaded.value = true
} catch (e) {
// 如果发生错误,将错误赋值给error引用,并使用Error对象表示未知错误
error.value = e instanceof Error ? e : new Error('Unknown error')
} finally {
loading.value = false // 只设置loading为false,不改变loaded的值
}

let timer: number | undefined
if (timeout) {
timer = setTimeout(() => {
if (!loaded.value) {
// 如果超时且组件未加载完成,则将超时错误赋值给error引用
error.value = new Error('timeout') // 使用Error对象表示超时错误
} else {
clearTimeout(timer)
}
}, timeout)
}
}

// 如果存在延迟,则使用setTimeout延迟执行loader函数,否则直接执行loader函数
if (delay) setTimeout(loaderFn, delay)
else loaderFn()

return () => {
if (loaded.value) {
// 如果组件已加载完成,则返回加载完成的组件类型InnerComp
return { type: InnerComp }
} else if (error.value) {
// 这里可以添加错误处理逻辑,例如显示一个错误组件或提示信息
return errorComponent || '错误' // 假设存在一个错误组件ErrorComponent
} else {
// 在组件加载过程中显示一个加载指示器或占位符
return loadingComponent || '加载中' // 假设存在一个加载指示器组件LoadingIndicator
}
}
}
}
}


3-8、Vue 组件高级应用和插件
https://mrhzq.github.io/职业上一二事/前端面试/前端八股文/3-8、Vue 组件高级应用和插件/
作者
黄智强
发布于
2024年1月13日
许可协议