原文链接(格式更好):《3-8 Vue 组件高级应用和插件》
组件高级应用
动态组件
官方文档:cn.vuejs.org/guide/essen…
基本语法
<template>
<div>
<!-- 将根据 comName 的值渲染对应的组件 -->
<component :is="comName" />
</div>
</template>
<script setup lang="ts">
import Content1 from './Content1'
const comName = Content1
</script>
使用场景
异步动态组件
官方文档:cn.vuejs.org/guide/compo…
<!-- <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
官方文档:cn.vuejs.org/guide/built…
作用:在多个组件间动态切换时缓存被移除的组件实例,不会触发创建、销毁的生命周期。
配合动态组件使用更佳
<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
使用,切换时将不会销毁组件,但每次还是会“新”创建,即不会有“销毁”的生命周期。
其他内置组件
官方文档:cn.vuejs.org/api/built-i…
Teleport - 传送门
官方文档:cn.vuejs.org/guide/built…
作用:将其插槽内容渲染到 DOM 中的另一个位置。
<teleport to="body">
<h1>我将显示在 body 元素内</h1>
</teleport>
Transition - 过渡态
官方文档:cn.vuejs.org/guide/built…
作用:制作基于状态变化的过渡和动画
<button @click="show = !show">Toggle</button>
<Transition>
<p v-if="show">hello</p>
</Transition>
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
Suspense - 悬停
官方文档:cn.vuejs.org/guide/built…
作用:用来在组件树中协调对异步依赖的处理
插件
基本使用
// custom.js
export default {
install(app, options) {
console.log(app, options)
// 其他业务逻辑...
}
}
// main.js
app.use(customPlugin, { xx: 1 })
场景使用
- 注册全局的组件
- 注册全局的方法,通过挂载到 Vue 的原型链上实现
- 注册全局的自定义指令
// 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) => {}) 注册自定义指令
}
}
面试题
手写异步组件
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
}
}
}
}
}