Vue 3.0 插槽

2022-03-04 09:04 更新

該頁面假設(shè)你已經(jīng)閱讀過了組件基礎(chǔ)。如果你還對組件不太了解,推薦你先閱讀它。

#插槽內(nèi)容

Vue 實現(xiàn)了一套內(nèi)容分發(fā)的 API,這套 API 的設(shè)計靈感源自 Web Components 規(guī)范草案,將 <slot> 元素作為承載分發(fā)內(nèi)容的出口。

它允許你像這樣合成組件:

  1. <todo-button>
  2. Add todo
  3. </todo-button>

然后在 <todo-button> 的模板中,你可能有:

  1. <!-- todo-button 組件模板 -->
  2. <button class="btn-primary">
  3. <slot></slot>
  4. </button>

當(dāng)組件渲染的時候,將會被替換為“Add Todo”。

  1. <!-- 渲染 HTML -->
  2. <button class="btn-primary">
  3. Add todo
  4. </button>

不過,字符串只是開始!插槽還可以包含任何模板代碼,包括 HTML:

  1. <todo-button>
  2. <!-- 添加一個Font Awesome 圖標(biāo) -->
  3. <i class="fas fa-plus"></i>
  4. Add todo
  5. </todo-button>

或其他組件

  1. <todo-button>
  2. <!-- 添加一個圖標(biāo)的組件 -->
  3. <font-awesome-icon name="plus"></font-awesome-icon>
  4. Add todo
  5. </todo-button>

如果 <todo-button> 的 template 中沒有包含一個 <slot> 元素,則該組件起始標(biāo)簽和結(jié)束標(biāo)簽之間的任何內(nèi)容都會被拋棄

  1. <!-- todo-button 組件模板 -->
  2. <button class="btn-primary">
  3. Create a new item
  4. </button>

  1. <todo-button>
  2. <!-- 以下文本不會渲染 -->
  3. Add todo
  4. </todo-button>

#渲染作用域

當(dāng)你想在一個插槽中使用數(shù)據(jù)時,例如:

  1. <todo-button>
  2. Delete a {{ item.name }}
  3. </todo-button>

該插槽可以訪問與模板其余部分相同的實例 property (即相同的“作用域”)。

Slot explanation diagram

插槽不能訪問 <todo-button> 的作用域。例如,嘗試訪問 action 將不起作用:

  1. <todo-button action="delete">
  2. Clicking here will {{ action }} an item
  3. <!-- `action` 未被定義,因為它的內(nèi)容是傳遞*到* <todo-button>,而不是*在* <todo-button>里定義的。 -->
  4. </todo-button>

作為一條規(guī)則,請記住:

父級模板里的所有內(nèi)容都是在父級作用域中編譯的;子模板里的所有內(nèi)容都是在子作用域中編譯的。

#后備內(nèi)容

有時為一個插槽設(shè)置具體的后備 (也就是默認(rèn)的) 內(nèi)容是很有用的,它只會在沒有提供內(nèi)容的時候被渲染。例如在一個 <submit-button> 組件中:

  1. <button type="submit">
  2. <slot></slot>
  3. </button>

我們可能希望這個 <button> 內(nèi)絕大多數(shù)情況下都渲染文本“Submit”。為了將“Submit”作為后備內(nèi)容,我們可以將它放在 <slot> 標(biāo)簽內(nèi):

  1. <button type="submit">
  2. <slot>Submit</slot>
  3. </button>

現(xiàn)在當(dāng)我在一個父級組件中使用 <submit-button > 并且不提供任何插槽內(nèi)容時:

  1. <submit-button></submit-button>

后備內(nèi)容“Submit”將會被渲染:

  1. <button type="submit">
  2. Submit
  3. </button>

但是如果我們提供內(nèi)容:

  1. <submit-button>
  2. Save
  3. </submit-button>

則這個提供的內(nèi)容將會被渲染從而取代后備內(nèi)容:

  1. <button type="submit">
  2. Save
  3. </button>

#具名插槽

有時我們需要多個插槽。例如對于一個帶有如下模板的 <base-layout> 組件:

  1. <div class="container">
  2. <header>
  3. <!-- 我們希望把頁頭放這里 -->
  4. </header>
  5. <main>
  6. <!-- 我們希望把主要內(nèi)容放這里 -->
  7. </main>
  8. <footer>
  9. <!-- 我們希望把頁腳放這里 -->
  10. </footer>
  11. </div>

對于這樣的情況,<slot> 元素有一個特殊的 attribute:name。這個 attribute 可以用來定義額外的插槽:

  1. <div class="container">
  2. <header>
  3. <slot name="header"></slot>
  4. </header>
  5. <main>
  6. <slot></slot>
  7. </main>
  8. <footer>
  9. <slot name="footer"></slot>
  10. </footer>
  11. </div>

一個不帶 name<slot> 出口會帶有隱含的名字“default”。

在向具名插槽提供內(nèi)容的時候,我們可以在一個 <template> 元素上使用 v-slot 指令,并以 v-slot 的參數(shù)的形式提供其名稱:

  1. <base-layout>
  2. <template v-slot:header>
  3. <h1>Here might be a page title</h1>
  4. </template>
  5. <template v-slot:default>
  6. <p>A paragraph for the main content.</p>
  7. <p>And another one.</p>
  8. </template>
  9. <template v-slot:footer>
  10. <p>Here's some contact info</p>
  11. </template>
  12. </base-layout>

現(xiàn)在 <template> 元素中的所有內(nèi)容都將會被傳入相應(yīng)的插槽。

渲染的 HTML 將會是:

  1. <div class="container">
  2. <header>
  3. <h1>Here might be a page title</h1>
  4. </header>
  5. <main>
  6. <p>A paragraph for the main content.</p>
  7. <p>And another one.</p>
  8. </main>
  9. <footer>
  10. <p>Here's some contact info</p>
  11. </footer>
  12. </div>

注意,v-slot 只能添加在 <template> (只有一種例外情況)

#作用域插槽

有時讓插槽內(nèi)容能夠訪問子組件中才有的數(shù)據(jù)是很有用的。當(dāng)一個組件被用來渲染一個項目數(shù)組時,這是一個常見的情況,我們希望能夠自定義每個項目的渲染方式。

例如,我們有一個組件,包含 todo-items 的列表。

  1. app.component('todo-list', {
  2. data() {
  3. return {
  4. items: ['Feed a cat', 'Buy milk']
  5. }
  6. },
  7. template: `
  8. <ul>
  9. <li v-for="(item, index) in items">
  10. {{ item }}
  11. </li>
  12. </ul>
  13. `
  14. })

我們可能需要替換插槽以在父組件上自定義它:

  1. <todo-list>
  2. <i class="fas fa-check"></i>
  3. <span class="green">{{ item }}</span>
  4. </todo-list>

但是,這是行不通的,因為只有 <todo-list> 組件可以訪問 item,我們將從其父組件提供槽內(nèi)容。

要使 item 可用于父級提供的 slot 內(nèi)容,我們可以添加一個 <slot> 元素并將其綁定為屬性:

  1. <ul>
  2. <li v-for="( item, index ) in items">
  3. <slot :item="item"></slot>
  4. </li>
  5. </ul>

綁定在 <slot > 元素上的 attribute 被稱為插槽 prop?,F(xiàn)在在父級作用域中,我們可以使用帶值的 v-slot 來定義我們提供的插槽 prop 的名字:

  1. <todo-list>
  2. <template v-slot:default="slotProps">
  3. <i class="fas fa-check"></i>
  4. <span class="green">{{ slotProps.item }}</span>
  5. </template>
  6. </todo-list>

Scoped slot diagram

在這個例子中,我們選擇將包含所有插槽 prop 的對象命名為 slotProps,但你也可以使用任意你喜歡的名字。

#獨占默認(rèn)插槽的縮寫語法

在上述情況下,當(dāng)被提供的內(nèi)容只有默認(rèn)插槽時,組件的標(biāo)簽才可以被當(dāng)作插槽的模板來使用。這樣我們就可以把 v-slot 直接用在組件上:

  1. <todo-list v-slot:default="slotProps">
  2. <i class="fas fa-check"></i>
  3. <span class="green">{{ slotProps.item }}</span>
  4. </todo-list>

這種寫法還可以更簡單。就像假定未指明的內(nèi)容對應(yīng)默認(rèn)插槽一樣,不帶參數(shù)的 v-slot 被假定對應(yīng)默認(rèn)插槽:

  1. <todo-list v-slot="slotProps">
  2. <i class="fas fa-check"></i>
  3. <span class="green">{{ slotProps.item }}</span>
  4. </todo-list>

注意默認(rèn)插槽的縮寫語法不能和具名插槽混用,因為它會導(dǎo)致作用域不明確:

  1. <!-- 無效,會導(dǎo)致警告 -->
  2. <todo-list v-slot="slotProps">
  3. <todo-list v-slot:default="slotProps">
  4. <i class="fas fa-check"></i>
  5. <span class="green">{{ slotProps.item }}</span>
  6. </todo-list>
  7. <template v-slot:other="otherSlotProps">
  8. slotProps is NOT available here
  9. </template>
  10. </todo-list>

只要出現(xiàn)多個插槽,請始終為所有的插槽使用完整的基于 <template> 的語法:

  1. <todo-list>
  2. <template v-slot:default="slotProps">
  3. <i class="fas fa-check"></i>
  4. <span class="green">{{ slotProps.item }}</span>
  5. </template>
  6. <template v-slot:other="otherSlotProps">
  7. ...
  8. </template>
  9. </todo-list>

#解構(gòu)插槽 Prop

作用域插槽的內(nèi)部工作原理是將你的插槽內(nèi)容包括在一個傳入單個參數(shù)的函數(shù)里:

  1. function (slotProps) {
  2. // ... 插槽內(nèi)容 ...
  3. }

這意味著 v-slot 的值實際上可以是任何能夠作為函數(shù)定義中的參數(shù)的 JavaScript 表達(dá)式。你也可以使用 ES2015 解構(gòu)來傳入具體的插槽 prop,如下:

  1. <todo-list v-slot="{ item }">
  2. <i class="fas fa-check"></i>
  3. <span class="green">{{ item }}</span>
  4. </todo-list>

這樣可以使模板更簡潔,尤其是在該插槽提供了多個 prop 的時候。它同樣開啟了 prop 重命名等其它可能,例如將 item 重命名為 todo

  1. <todo-list v-slot="{ item: todo }">
  2. <i class="fas fa-check"></i>
  3. <span class="green">{{ todo }}</span>
  4. </todo-list>

你甚至可以定義后備內(nèi)容,用于插槽 prop 是 undefined 的情形:

  1. <todo-list v-slot="{ item = 'Placeholder' }">
  2. <i class="fas fa-check"></i>
  3. <span class="green">{{ item }}</span>
  4. </todo-list>

#動態(tài)插槽名

動態(tài)指令參數(shù)也可以用在 v-slot 上,來定義動態(tài)的插槽名:

  1. <base-layout>
  2. <template v-slot:[dynamicSlotName]>
  3. ...
  4. </template>
  5. </base-layout>

#具名插槽的縮寫

v-onv-bind 一樣,v-slot 也有縮寫,即把參數(shù)之前的所有內(nèi)容 (v-slot:) 替換為字符 #。例如 v-slot:header 可以被重寫為 #header

  1. <base-layout>
  2. <template #header>
  3. <h1>Here might be a page title</h1>
  4. </template>
  5. <template #default>
  6. <p>A paragraph for the main content.</p>
  7. <p>And another one.</p>
  8. </template>
  9. <template #footer>
  10. <p>Here's some contact info</p>
  11. </template>
  12. </base-layout>

然而,和其它指令一樣,該縮寫只在其有參數(shù)的時候才可用。這意味著以下語法是無效的:

  1. <!-- This will trigger a warning -->
  2. <todo-list #="{ item }">
  3. <i class="fas fa-check"></i>
  4. <span class="green">{{ item }}</span>
  5. </todo-list>

如果你希望使用縮寫的話,你必須始終以明確插槽名取而代之:

  1. <todo-list #default="{ item }">
  2. <i class="fas fa-check"></i>
  3. <span class="green">{{ item }}</span>
  4. </todo-list>
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號