本篇目錄:
- 插槽(Slot)
- 插槽內容
- 作用域
- 具名插槽
- 作用域插槽
- 獨占默認插槽的縮寫語法
- 解構插槽Prop
- 使用場景舉例
- 動態插槽名
- 具名插槽縮寫
- 動態組件&keep-alive
- 異步組件
- 處理加載狀態
- 小結
插槽(Slot)
定義一個名child子組件,為該子組件添加內容應該在子組件的template中定義,直接在父組件的<child>
標簽中定義的內容不會被渲染。
在子組件中通過加入<slot>
元素占位,便能夠渲染父組件中子組件標簽中的內容了。
插槽內容
- 任何模版代碼
- HTML代碼
- 其他組件
插槽可以有默認內容,當在父組件中沒有提供內容的時候,來進行顯示。
<!-- submit-button -->
<button type="submit">
<slot>Submit</slot>
</button>
1.
<submit-button></submit-button>
⬇️
<button type="submit">
Submit
</button>
2.
<submit-button>
Save
</submit-button>
⬇️
<button type="submit">
Save
</button>
作用域
父級模板里的所有內容都是在父級作用域中編譯的;子模板里的所有內容都是在子作用域中編譯的。
具名插槽
試想,我們有一個帶有如下模版的<base-layout>
組件
<div class="container">
<header>
<!-- 我們希望把頁頭放這里 -->
</header>
<main>
<!-- 我們希望把主要內容放這里 -->
</main>
<footer>
<!-- 我們希望把頁腳放這里 -->
</footer>
</div>
可以看到,在組件中顯示的內容是划分不同的部位的,這個時候就需要使用到<slot>
元素的一個特有的屬性:name
來實現了。這個特性可以用來定義額外的插槽。
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
一個不帶 name 的 <slot>
出口會帶有隱含的名字“default”。
在向具名插槽提供內容的時候,我們可以在一個 <template>
元素上使用 v-slot
指令,並以 v-slot
的參數的形式提供其名稱:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
現在 <template>
元素中的所有內容都將會被傳入相應的插槽。任何沒有被包裹在帶有 v-slot
的 <template>
中的內容都會被視為默認插槽的內容。
當然,也可以將默認插槽的內容通過v-slot:default
包裹起來。
v-slot
只能添加在一個<template>
上
作用域插槽
當我們希望能夠讓插槽內容能夠訪問子組件中才有的數據時,我們可以將數據作為一個<slot>
元素的特性綁定上去
<span>
<slot v-bind:user="user">
{{ user.name }}
</slot>
</span>
綁定在<slot>
元素上的特性被稱為插槽prop。此時我們在父組件中通過給v-slot
帶一個值來定義我們提供的插槽prop的名字。
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.age }}
</template>
</current-user>
獨占默認插槽的縮寫語法
當被提供的內容只有默認插槽時,上面的寫法可以被簡化來寫
<!-- 簡化版 -->
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
<!-- 終極簡化版 -->
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
</current-user>
需要注意兩點:
- 簡化寫法不能和具名插槽混用,作用域不明確
- 出現多個插槽時,所有插槽都使用完整的基於
<template>
語法
解構插槽Prop
作用域插槽的內部工作原理是將你的插槽內容包括在一個傳入單個參數的函數里:
function (slotProps) {
// 插槽內容
}
這意味着 v-slot
的值實際上可以是任何能夠作為函數定義中的參數的 JavaScript
表達式。所以在支持的環境下 (單文件組件或現代瀏覽器),你也可以使用 ES2015 解構來傳入具體的插槽 prop
,如下:
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
⬇️
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
使用場景舉例
插槽 prop
允許我們將插槽轉換為可復用的模板,這些模板可以基於輸入的 prop
渲染出不同的內容。
這在設計封裝數據邏輯同時允許父級組件自定義部分布局的可復用組件時是最有用的。
動態插槽名
動態指令參數也可以用在 v-slot
上,來定義動態的插槽名
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
具名插槽縮寫
v-slot
可以縮寫為#
。
縮寫方式只有在有參數的時候才可以使用
<!-- 這樣會觸發一個警告 -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>
<!-- 這樣是正確的 -->
<current-user #default="{ user }">
{{ user.firstName }}
</current-user>
動態組件&keep-alive
當在這些組件之間切換的時候,你有時會想保持這些組件的狀態,以避免反復重渲染導致的性能問題。為了解決這個問題,我們可以用一個<keep-alive>
元素將動態組件包裹起來
<!-- 失活的組件將會被緩存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
注意這個
<keep-alive>
要求被切換到的組件都有自己的名字,不論是通過組件的 name 選項還是局部/全局注冊。
更加詳細的說明在我們之后的實戰過程中遇到的話,再進行專門解說。
異步組件
在實際的項目過程中,我們往往會將一系列的功能分割成一個個小的代碼塊,希望只有在需要的時候才去加載。為了達成這個目的,Vue允許我們以一個工廠函數的方式定義我們的組件,這個工廠函數會異步解析組件的定義。
Vue只有在這個組件需要渲染的時候才會觸發這個工廠函數,而且會把結果緩存起來供之后使用。
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回調傳遞組件定義
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
其實,這個過程有些類似於我們設計一個異步函數,這個工廠函數會收到一個resolve回調,這個回調在我們從服務器獲取到組件定義的時候被調用,當加載失敗的時候我們也可以調用reject(reason)。
一個推薦的做法是異步組件和webpack的code-splitting功能結合使用
Vue.component('async-webpack-example', function (resolve) {
// 這個特殊的 `require` 語法將會告訴 webpack
// 自動將你的構建代碼切割成多個包,這些包
// 會通過 Ajax 請求加載
require(['./my-async-component'], resolve)
})
同樣,也可以在工廠函數中返回一個Promise
Vue.component(
'async-webpack-example',
// 這個 `import` 函數會返回一個 `Promise` 對象。
() => import('./my-async-component')
)
處理加載狀態
上面的工廠函數可以返回一個下面格式的對象
const AsyncComponent = () => ({
// 需要加載的組件 (應該是一個 `Promise` 對象)
component: import('./MyComponent.vue'),
// 異步組件加載時使用的組件
loading: LoadingComponent,
// 加載失敗時使用的組件
error: ErrorComponent,
// 展示加載時組件的延時時間。默認值是 200 (毫秒)
delay: 200,
// 如果提供了超時時間且組件加載也超時了,
// 則使用加載失敗時使用的組件。默認值是:`Infinity`
timeout: 3000
})
小結
本篇我們主要圍繞着Vue組件中的插槽和相關動態組件、異步組件的內容進行了梳理。通過這兩篇的整理,我們對於Vue組件有了一個比較整體的了解。后續我們會在實戰過程中針對具體的點再進行詳細的說明。