Vue 3.0 插槽

2022-03-04 09:04 更新

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

#插槽內(nèi)容

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

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

<todo-button>
  Add todo
</todo-button>

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

<!-- todo-button 組件模板 -->
<button class="btn-primary">
  <slot></slot>
</button>

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

<!-- 渲染 HTML -->
<button class="btn-primary">
  Add todo
</button>

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

<todo-button>
  <!-- 添加一個(gè)Font Awesome 圖標(biāo) -->
  <i class="fas fa-plus"></i>
  Add todo
</todo-button>

或其他組件

<todo-button>
    <!-- 添加一個(gè)圖標(biāo)的組件 -->
  <font-awesome-icon name="plus"></font-awesome-icon>
  Add todo
</todo-button>

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

<!-- todo-button 組件模板 -->


<button class="btn-primary">
  Create a new item
</button>

<todo-button>
  <!-- 以下文本不會(huì)渲染 -->
  Add todo
</todo-button>

#渲染作用域

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

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

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

Slot explanation diagram

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

<todo-button action="delete">
  Clicking here will {{ action }} an item
  <!-- `action` 未被定義,因?yàn)樗膬?nèi)容是傳遞*到* <todo-button>,而不是*在* <todo-button>里定義的。  -->
</todo-button>

作為一條規(guī)則,請(qǐng)記?。?/p>

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

#后備內(nèi)容

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

<button type="submit">
  <slot></slot>
</button>

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

<button type="submit">
  <slot>Submit</slot>
</button>

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

<submit-button></submit-button>

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

<button type="submit">
  Submit
</button>

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

<submit-button>
  Save
</submit-button>

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

<button type="submit">
  Save
</button>

#具名插槽

有時(shí)我們需要多個(gè)插槽。例如對(duì)于一個(gè)帶有如下模板的 <base-layout> 組件:

<div class="container">
  <header>
    <!-- 我們希望把頁(yè)頭放這里 -->
  </header>
  <main>
    <!-- 我們希望把主要內(nèi)容放這里 -->
  </main>
  <footer>
    <!-- 我們希望把頁(yè)腳放這里 -->
  </footer>
</div>

對(duì)于這樣的情況,<slot> 元素有一個(gè)特殊的 attribute:name。這個(gè) attribute 可以用來(lái)定義額外的插槽:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

一個(gè)不帶 name<slot> 出口會(huì)帶有隱含的名字“default”。

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

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>


  <template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>


  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

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

渲染的 HTML 將會(huì)是:

<div class="container">
  <header>
    <h1>Here might be a page title</h1>
  </header>
  <main>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </main>
  <footer>
    <p>Here's some contact info</p>
  </footer>
</div>

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

#作用域插槽

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

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

app.component('todo-list', {
  data() {
    return {
      items: ['Feed a cat', 'Buy milk']
    }
  },
  template: `
    <ul>
      <li v-for="(item, index) in items">
        {{ item }}
      </li>
    </ul>
  `
})

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

<todo-list>
  <i class="fas fa-check"></i>
  <span class="green">{{ item }}</span>
</todo-list>

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

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

<ul>
  <li v-for="( item, index ) in items">
    <slot :item="item"></slot>
  </li>
</ul>

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

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

Scoped slot diagram

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

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

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

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

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

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

注意默認(rèn)插槽的縮寫語(yǔ)法不能和具名插槽混用,因?yàn)樗鼤?huì)導(dǎo)致作用域不明確:

<!-- 無(wú)效,會(huì)導(dǎo)致警告 -->
<todo-list v-slot="slotProps">
  <todo-list v-slot:default="slotProps">
    <i class="fas fa-check"></i>
    <span class="green">{{ slotProps.item }}</span>
  </todo-list>
  <template v-slot:other="otherSlotProps">
    slotProps is NOT available here
  </template>
</todo-list>

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

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


  <template v-slot:other="otherSlotProps">
    ...
  </template>
</todo-list>

#解構(gòu)插槽 Prop

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

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

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

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

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

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

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

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

#動(dòng)態(tài)插槽名

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

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
</base-layout>

#具名插槽的縮寫

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

<base-layout>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>


  <template #default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>


  <template #footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

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

<!-- This will trigger a warning -->


<todo-list #="{ item }">
  <i class="fas fa-check"></i>
  <span class="green">{{ item }}</span>
</todo-list>

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

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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)