本章概要
- 动态组件
- 异步组件
10.7 动态组件
在页面应用程序中,经常会遇到多标签页面,在Vue.js 中,可以通过动态组件来实现。组件的动态切换时通过在 component 元素上使用 is 属性来实现的。
app.component('tab-introduce', {
data() {
return {
content: '红楼梦'
}
},
template: '<div><input v-model="content"></div>'
})
app.component('tab-comment', {
template: '<div>这是一本好书</div>'
})
app.component('tab-qa', {
template: '<div>有人看过吗?怎么样?</div>'
})
组件的模板使用了一个 input 元素,便于修改内容。
在根实例中定义了两个数据属性和一个计算属性,主要是为了便于使用 v-for 指令循环渲染 button 按钮,以及动态切换组件。
const app = Vue.createApp({
data() {
return {
currentTab: 'introduce',
tabs: [
{ title: 'introduce', displayName: '图书介绍' },
{ title: 'comment', displayName: '图书评价' },
{ title: 'qa', displayName: '图书问答' }
]
}
},
computed: {
currentTabComponent: function () {
return 'tab-' + this.currentTab
}
}
})
...
app.mount('#app');
数据属性 currentTab 代表当前的标签页,tabs 是一个数组对象,通过 v-for 指令渲染代表标签的 3 个按钮,计算属性 currentTabComponent 代表当前选中的组件。
接下来就是在与实例关联的 DOM 模板中渲染按钮,以及动态切换组件的代码。
<div id="app">
<button v-for="tab in tabs" :key="tab.title" :class="['tab-button', { active: currentTab === tab.title }]"
@click="currentTab = tab.title">
{{ tab.displayName }}
</button>
<keep-alive>
<component :is="currentTabComponent" class="tab">
</component>
</keep-alive>
</div>
当点击某个标签按钮时,更改数据属性 currentTab 的值,这将导致计算属性 currentTabComponent 的值更新。
component 元素的 is 属性使用 v-bind 指令绑定到一个已注册的名字上,随着计算属性 currentTabComponent 值的改变,组件也就自动切换了。
剩下的代码就是 CSS 样式的设置了。完整代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>动态组件</title>
<style>
div {
width: 400px;
}
.tab-button {
padding: 6px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border: solid 1px #ccc;
cursor: pointer;
background: #f0f0f0;
margin-bottom: -1px;
margin-right: -1px;
}
.tab-button:hover {
background: #e0e0e0;
}
.tab-button .active {
background: #cdcdcd;
}
.tab {
border: solid 1px #ccc;
padding: 10px;
}
</style>
</head>
<body>
<div id="app">
<button v-for="tab in tabs" :key="tab.title" :class="['tab-button', { active: currentTab === tab.title }]"
@click="currentTab = tab.title">
{{ tab.displayName }}
</button>
<component :is="currentTabComponent" class="tab">
</component>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({
data() {
return {
currentTab: 'introduce',
tabs: [
{ title: 'introduce', displayName: '图书介绍' },
{ title: 'comment', displayName: '图书评价' },
{ title: 'qa', displayName: '图书问答' }
]
}
},
computed: {
currentTabComponent: function () {
return 'tab-' + this.currentTab
}
}
})
app.component('tab-introduce', {
data() {
return {
content: '红楼梦'
}
},
template: '<div><input v-model="content"></div>'
})
app.component('tab-comment', {
template: '<div>这是一本好书</div>'
})
app.component('tab-qa', {
template: '<div>有人看过吗?怎么样?</div>'
})
app.mount('#app');
</script>
</body>
</html>
修改图书介绍的内容,修改后切换到其它标签页,然后再切换过来,发现之前修改的内容并没保存下来。
如下:
这是因为每次切换新标签的时候,Vue 都创建一个新的 currentTabComponent 实例。
在本例中,希望组件在切换的时候,可以保持组件的状态,以避免重复渲染导致的性能问题,也为了让用户体验更好。
要解决这个问题,可以用一个 keep-alive 元素将动态组件包裹起来。如下:
<keep-alive>
<component :is="currentTabComponent" class="tab">
</component>
</keep-alive>
10.8 异步组件
在大型的应用中,可能需要将应用分割成较小的代码块,并且只在需要时才从服务器加载组件。
为了实现这一点,Vue 给出了一个 defineAsyncComponent() 方法,该方法接受一个返回 Promise 的工厂函数,当从服务器检索到组件定义的时候,应该调用 Promise 的 resolve 回调。如下:
const app = Vue.createApp({})
const AsyncComp = Vue.defineAsyncComponent(
()=> new Promise((resolve,reject)=>{
resolve({
template:'<div>只有头发不绿的浩克</div>'
})
})
)
app.component('async-example',AsyncComp);
当然也可以调用 reject(reason) 指示加载失败。
也可以在工厂函数中返回一个 Promise ,因此对于 Webpack 2 或更高版本,以及 ES6 语法,可以执行一下操作。
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(()=>
import('./components/AsyncComponent.vue')
)
app.component('async-component',AsyncComp)
在本地注册组件时,也可以使用 defineAsyncComponent() 方法。如下:
import { defineAsyncComponent } from 'vue'
createApp({
//...
components:{
AsyncComponent:defineAsyncComponent(()=>
import('./components/AsyncComponent.vue')
)
}
})
defineAsyncComponent() 方法还可以接受一个对象作为参数。如下:
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent({
// 工厂函数
loder:()=>import('./Foo.vue'),
// 加载异步组件时要使用的组件
loadingComponent:loadingComponent,
// 加载失败时要使用的组件
errorComponent:ErrorComponent,
// 显示加载组件前的延迟时间,默认是 200ms
delay:200,
// 如果给出了超时值并超过超时值,则显示错误组件。默认没有超时值
timeout:3000,
// 定义组件是否可悬挂,默认为true
suspensible:false,
/**
* @param {*} error 错误消息对象
* @param {*} retry 指示当加载器 promise 拒绝时异步组件是否重试的函数
* @param {*} fail 失败结束
* @param {*} 允许重试的最大尝试次数
*
*/
onerror(error,retry,fail,attempts){
if(error.message.math(/fetch/) && attempts <= 3 ){
// 获取错误时重试,最多尝试 3 次
retry()
}else{
// 请注意,重试/失败类似于 promise 的 resolve / reject
// 要继续进行错误处理,必须调用其中的一个
fail();
}
}
})