> 該頁面假設你已經閱讀過了[組件基礎](818090.html)。如果你還對組件不太了解,推薦你先閱讀它。
## 在動態組件上使用 `keep-alive`
我們之前曾經在一個多標簽的界面中使用 `is` 特性來切換不同的組件:
```html
<component v-bind:is="currentTabComponent"></component>
```
當在這些組件之間切換的時候,你有時會想保持這些組件的狀態,以避免反復重渲染導致的性能問題。例如我們來展開說一說這個多標簽界面:
{% raw %}
<div id="dynamic-component-demo" class="demo">
<button
v-for="tab in tabs"
v-bind:key="tab"
v-bind:class="['dynamic-component-demo-tab-button', { 'dynamic-component-demo-active': currentTab === tab }]"
v-on:click="currentTab = tab"
>{{ tab }}</button>
<component
v-bind:is="currentTabComponent"
class="dynamic-component-demo-tab"
></component>
</div>
<script>
Vue.component('tab-posts', {
data: function () {
return {
posts: [
{
id: 1,
title: 'Cat Ipsum',
content: '<p>Dont wait for the storm to pass, dance in the rain kick up litter decide to want nothing to do with my owner today demand to be let outside at once, and expect owner to wait for me as i think about it cat cat moo moo lick ears lick paws so make meme, make cute face but lick the other cats. Kitty poochy chase imaginary bugs, but stand in front of the computer screen. Sweet beast cat dog hate mouse eat string barf pillow no baths hate everything stare at guinea pigs. My left donut is missing, as is my right loved it, hated it, loved it, hated it scoot butt on the rug cat not kitten around</p>'
},
{
id: 2,
title: 'Hipster Ipsum',
content: '<p>Bushwick blue bottle scenester helvetica ugh, meh four loko. Put a bird on it lumbersexual franzen shabby chic, street art knausgaard trust fund shaman scenester live-edge mixtape taxidermy viral yuccie succulents. Keytar poke bicycle rights, crucifix street art neutra air plant PBR&B hoodie plaid venmo. Tilde swag art party fanny pack vinyl letterpress venmo jean shorts offal mumblecore. Vice blog gentrify mlkshk tattooed occupy snackwave, hoodie craft beer next level migas 8-bit chartreuse. Trust fund food truck drinking vinegar gochujang.</p>'
},
{
id: 3,
title: 'Cupcake Ipsum',
content: '<p>Icing dessert soufflé lollipop chocolate bar sweet tart cake chupa chups. Soufflé marzipan jelly beans croissant toffee marzipan cupcake icing fruitcake. Muffin cake pudding soufflé wafer jelly bear claw sesame snaps marshmallow. Marzipan soufflé croissant lemon drops gingerbread sugar plum lemon drops apple pie gummies. Sweet roll donut oat cake toffee cake. Liquorice candy macaroon toffee cookie marzipan.</p>'
}
],
selectedPost: null
}
},
template: '\
<div class="dynamic-component-demo-posts-tab">\
<ul class="dynamic-component-demo-posts-sidebar">\
<li\
v-for="post in posts"\
v-bind:key="post.id"\
v-bind:class="{ \'dynamic-component-demo-active\': post === selectedPost }"\
v-on:click="selectedPost = post"\
>\
{{ post.title }}\
</li>\
</ul>\
<div class="dynamic-component-demo-post-container">\
<div \
v-if="selectedPost"\
class="dynamic-component-demo-post"\
>\
<h3>{{ selectedPost.title }}</h3>\
<div v-html="selectedPost.content"></div>\
</div>\
<strong v-else>\
Click on a blog title to the left to view it.\
</strong>\
</div>\
</div>\
'
})
Vue.component('tab-archive', {
template: '<div>Archive component</div>'
})
new Vue({
el: '#dynamic-component-demo',
data: {
currentTab: 'Posts',
tabs: ['Posts', 'Archive']
},
computed: {
currentTabComponent: function () {
return 'tab-' + this.currentTab.toLowerCase()
}
}
})
</script>
<style>
.dynamic-component-demo-tab-button {
padding: 6px 10px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border: 1px solid #ccc;
cursor: pointer;
background: #f0f0f0;
margin-bottom: -1px;
margin-right: -1px;
}
.dynamic-component-demo-tab-button:hover {
background: #e0e0e0;
}
.dynamic-component-demo-tab-button.dynamic-component-demo-active {
background: #e0e0e0;
}
.dynamic-component-demo-tab {
border: 1px solid #ccc;
padding: 10px;
}
.dynamic-component-demo-posts-tab {
display: flex;
}
.dynamic-component-demo-posts-sidebar {
max-width: 40vw;
margin: 0 !important;
padding: 0 10px 0 0 !important;
list-style-type: none;
border-right: 1px solid #ccc;
}
.dynamic-component-demo-posts-sidebar li {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
cursor: pointer;
}
.dynamic-component-demo-posts-sidebar li:hover {
background: #eee;
}
.dynamic-component-demo-posts-sidebar li.dynamic-component-demo-active {
background: lightblue;
}
.dynamic-component-demo-post-container {
padding-left: 10px;
}
.dynamic-component-demo-post > :first-child {
margin-top: 0 !important;
padding-top: 0 !important;
}
</style>
{% endraw %}
你會注意到,如果你選擇了一篇文章,切換到 *Archive* 標簽,然后再切換回 *Posts*,是不會繼續展示你之前選擇的文章的。這是因為你每次切換新標簽的時候,Vue 都創建了一個新的 `currentTabComponent` 實例。
重新創建動態組件的行為通常是非常有用的,但是在這個案例中,我們更希望那些標簽的組件實例能夠被在它們第一次被創建的時候緩存下來。為了解決這個問題,我們可以用一個 `<keep-alive>` 元素將其動態組件包裹起來。
``` html
<!-- 失活的組件將會被緩存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
```
來看看修改后的結果:
{% raw %}
<div id="dynamic-component-keep-alive-demo" class="demo">
<button
v-for="tab in tabs"
v-bind:key="tab"
v-bind:class="['dynamic-component-demo-tab-button', { 'dynamic-component-demo-active': currentTab === tab }]"
v-on:click="currentTab = tab"
>{{ tab }}</button>
<keep-alive>
<component
v-bind:is="currentTabComponent"
class="dynamic-component-demo-tab"
></component>
</keep-alive>
</div>
<script>
new Vue({
el: '#dynamic-component-keep-alive-demo',
data: {
currentTab: 'Posts',
tabs: ['Posts', 'Archive']
},
computed: {
currentTabComponent: function () {
return 'tab-' + this.currentTab.toLowerCase()
}
}
})
</script>
{% endraw %}
現在這個 *Posts* 標簽保持了它的狀態 (被選中的文章) 甚至當它未被渲染時也是如此。你可以在[這個 fiddle](https://jsfiddle.net/chrisvfritz/Lp20op9o/) 查閱到完整的代碼。
<p class="tip">注意這個 `<keep-alive>` 要求被切換到的組件都有自己的名字,不論是通過組件的 `name` 選項還是局部/全局注冊。</p>
你可以在 [API 參考文檔](../api/#keep-alive) 查閱更多關于 `<keep-alive>` 的細節。
## 異步組件
在大型應用中,我們可能需要將應用分割成小一些的代碼塊,并且只在需要的時候才從服務器加載一個模塊。為了簡化,Vue 允許你以一個工廠函數的方式定義你的組件,這個工廠函數會異步解析你的組件定義。Vue 只有在這個組件需要被渲染的時候才會觸發該工廠函數,且會把結果緩存起來供未來重渲染。例如:
``` js
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回調傳遞組件定義
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
```
如你所見,這個工廠函數會收到一個 `resolve` 回調,這個回調函數會在你從服務器得到組件定義的時候被調用。你也可以調用 `reject(reason)` 來表示加載失敗。這里的 `setTimeout` 是為了演示用的,如何獲取組件取決于你自己。一個推薦的做法是將異步組件和 [webpack 的 code-splitting 功能](https://webpack.js.org/guides/code-splitting/)一起配合使用:
``` js
Vue.component('async-webpack-example', function (resolve) {
?// 這個特殊的 `require` 語法將會告訴 webpack
// 自動將你的構建代碼切割成多個包,這些包
// 會通過 Ajax 請求加載
require(['./my-async-component'], resolve)
})
```
你也可以在工廠函數中返回一個 `Promise`,所以把 webpack 2 和 ES2015 語法加在一起,我們可以寫成這樣:
``` js
Vue.component(
'async-webpack-example',
// 這個 `import` 函數會返回一個 `Promise` 對象。
() => import('./my-async-component')
)
```
當使用[局部注冊](components.html#本地注冊)的時候,你也可以直接提供一個返回 `Promise` 的函數:
``` js
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
```
<p class="tip">如果你是一個 <strong>Browserify</strong> 用戶同時喜歡使用異步組件,很不幸這個工具的作者[明確表示](https://github.com/substack/node-browserify/issues/58#issuecomment-21978224)異步加載“并不會被 Browserify 支持”,至少官方不會。Browserify 社區已經找到了[一些變通方案](https://github.com/vuejs/vuejs.org/issues/620),這些方案可能會對已存在的復雜應用有幫助。對于其它的場景,我們推薦直接使用 webpack,以擁有內建的被作為第一公民的異步支持。</p>
### 處理加載狀態
> 2.3.0+ 新增
這里的異步組件工廠函數也可以返回一個如下格式的對象:
``` js
const AsyncComponent = () => ({
// 需要加載的組件 (應該是一個 `Promise` 對象)
component: import('./MyComponent.vue'),
// 異步組件加載時使用的組件
loading: LoadingComponent,
// 加載失敗時使用的組件
error: ErrorComponent,
// 展示加載時組件的延時時間。默認值是 200 (毫秒)
delay: 200,
// 如果提供了超時時間且組件加載也超時了,
// 則使用加載失敗時使用的組件。默認值是:`Infinity`
timeout: 3000
})
```
> 注意如果你希望在 [Vue Router](https://github.com/vuejs/vue-router) 的路由組件中使用上述語法的話,你必須使用 Vue Router 2.4.0+ 版本。
- 寫在前面
- 基礎
- 安裝
- 介紹
- Vue實例
- 模板語法
- 計算屬性和偵聽器
- Class 與 Style 綁定
- 條件渲染
- 列表渲染
- 事件處理
- 表單輸入綁定
- 組件基礎
- 深入了解組件
- 組件注冊
- Prop
- 自定義事件
- 插槽
- 動態組件 & 異步組件
- 處理邊界情況
- 過渡 & 動畫
- 進入/離開 & 列表過渡
- 狀態過渡
- 可復用性 & 組合
- 混入
- 自定義指令
- 渲染函數 & JSX
- 插件
- 過濾器
- 工具
- 生產環境部署
- 單文件組件
- 單元測試
- TypeScript 支持
- 規模化
- 路由
- 狀態管理
- 服務端渲染
- 內在
- 深入響應式原理
- 遷移
- 從 Vue 1.x 遷移
- 從 Vue Router 0.7.x 遷移
- 從 Vuex 0.6.x 遷移到 1.0
- 更多
- 對比其他框架
- 加入 Vue.js 社區
- 開發團隊