插槽


該頁面假設你已經閱讀過了元件基礎。如果你還對組件不太了解,推薦你先閱讀它。

在 2.6.0 中,我們為具名插槽和作用域插槽引入了一個新的統一的語法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 這兩個目前已被廢棄但未被移除且仍在文件中的功能。新語法的由來可查閱這份 RFC


目錄

  • 編譯作用域

    • #後備內容

    • 具名插槽

  • 作用域插槽

  • ##獨佔默認插槽的縮寫語法

  • 解構插槽Prop

  • #動態插槽名稱
    • 具名插槽的縮寫
    • 其它範例


已廢棄了的語法


#帶有slot 特性的具名插槽


帶有slot-scope 特性的作用域插槽

插槽內容

##########Vue 實作了一套內容分發的API,這套API 的設計靈感源自###Web Components 規範草案###,將###<slot>### 元素作為承載分發內容的出口。 ######它允許你像這樣合成元件:###
<navigation-link url="/profile">
  Your Profile
</navigation-link>
###然後你在###<navigation-link>### 的範本中可能會寫成:###
<a
  v-bind:href="url"
  class="nav-link"
>
  <slot></slot>
</a>
## #當元件渲染的時候,###<slot></slot>### 將會被替換為「Your Profile」。插槽內可以包含任何模板程式碼,包括 HTML:###
<navigation-link url="/profile">
  <!-- 添加一个 Font Awesome 图标 -->
  <span class="fa fa-user"></span>
  Your Profile
</navigation-link>
###甚至是其它的元件:###
<navigation-link url="/profile">
  <!-- 添加一个图标的组件 -->
  <font-awesome-icon name="user"></font-awesome-icon>
  Your Profile
</navigation-link>

如果 <navigation-link> 沒有包含一個 <slot> 元素,則該元件起始標籤和結束標籤之間的任何內容都會被拋棄。


編譯作用域


#當你想要在一個插槽中使用資料時,例如:

<navigation-link url="/profile">
  Logged in as {{ user.name }}
</navigation-link>

該插槽跟模板的其它地方一樣可以存取相同的實例屬性(也就是相同的「作用域」),而不能存取<navigation-link> 的作用域。例如url 是無法存取的:

<navigation-link url="/profile">
  Clicking here will send you to: {{ url }}
  <!--
  这里的 `url` 会是 undefined,因为 "/profile" 是
  _传递给_ <navigation-link> 的而不是
  在 <navigation-link> 组件*内部*定义的。
  -->
</navigation-link>

作為一條規則,請記住:

#父級範本裡的所有內容都是在父級作用域中編譯的;子模板裡的所有內容都是在子作用域中編譯的。


後備內容


##有時會為一個插槽設置具體的後備(也就是預設的) 內容是很有用的,它只會在沒有提供內容的時候被渲染。例如在一個

<submit-button> 元件:

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

我們可能希望這個

<button> 內絕大多數情況下都渲染文字「Submit 」。為了將「Submit」當作後備內容,我們可以將它放在<slot> 標籤內:

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

現在當我在一個父級元件中使用

< submit-button> 且不提供任何插槽內容時:

<submit-button></submit-button>

後備內容「Submit」將會被渲染:

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

但如果我們提供內容:

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

則這個提供的內容將會被渲染從而取代後備內容:

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


#具名插槽


自 2.6.0 起已更新。已廢棄的使用 

slot 特性的語法在這裡

有時我們需要多個插槽。例如對於一個帶有以下模板的

<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> 中的內容都會被視為預設插槽的內容。

然而,如果你希望更明確一些,仍然可以在一個<template> 中包裹預設插槽的內容:

<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>

任何一種寫法都會渲染:

<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> 上(只有一種例外情況),這一點和已經廢棄的slot 特性不同。


作用域插槽


#自 2.6.0 起有所更新。已廢棄的使用 slot-scope 特性的語法在這裡

有時讓插槽內容能夠存取子元件中才有的資料是很有用的。例如,設想一個帶有以下模板的<current-user> 元件:

<span>
  <slot>{{ user.lastName }}</slot>
</span>

我們想讓它的後備內容顯示用戶的名,以取代正常情況下用戶的姓,如下:

<current-user>
  {{ user.firstName }}
</current-user>

然而上述程式碼不會正常運作,因為只有<current-user> 元件可以存取user 而我們提供的內容是在父級渲染的。

為了讓user 在父級的插槽內容中可用,我們可以將user 作為<slot> 元素的一個特性綁定上去:

<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>

綁定在<slot> 元素上的特性稱為插槽prop。現在在父級作用域中,我們可以給v-slot 帶一個值來定義我們提供的插槽prop 的名字:

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

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


獨佔預設插槽的縮寫語法

在上述情況下,當被提供的內容只有預設插槽時,組件的標籤才可以當作插槽的模板來使用。這樣我們就可以把 v-slot 直接用在元件上:

<current-user v-slot:default="slotProps">
  {{ slotProps.user.firstName }}
</current-user>

這種寫法還可以更簡單。就像假定未指明的內容對應預設插槽一樣,不帶參數的v-slot 被假定對應預設插槽:

<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
</current-user>

注意預設插槽的縮寫語法不能和具名插槽混用,因為它會導致作用域不明確:

<!-- 无效,会导致警告 -->
<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
  <template v-slot:other="otherSlotProps">
    slotProps is NOT available here
  </template>
</current-user>

只要出現多個插槽,請始終為所有的插槽使用完整的基於<template> ; 的語法:

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
  <template v-slot:other="otherSlotProps">
    ...
  </template>
</current-user>


#解構插槽Prop

作用域插槽的內部運作原理是將你的插槽內容納入一個傳入單一參數的函數裡:

function (slotProps) {
  // 插槽内容
}

這意味著 v-slot 的值實際上可以是任何能夠作為函數定義中的參數的 JavaScript 表達式。所以在支援的環境下(單一檔案元件現代瀏覽器),你也可以使用ES2015 解構來傳入具體的插槽p​​rop,如下:

<current-user v-slot="{ user }">
  {{ user.firstName }}
</current-user>

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

<current-user v-slot="{ user: person }">
  {{ person.firstName }}
</current-user>

你甚至可以定義後備內容,用於插槽prop是undefined 的情形:

<current-user v-slot="{ user = { firstName: 'Guest' } }">
  {{ user.firstName }}
</current-user>


#動態插槽名稱


##2.6 .0 新增

動態指令參數也可以用在v-slot 上,定義動態的插槽名稱:

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


具名插槽的縮寫


#2.6.0 新增

v-onv-bind 一樣,v-slot 也有縮寫,也就是把參數之前的所有內容(v -slot:) 替換為字元#。例如v-slot:header 可以被重寫為#header

<base-layout>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>
  <p>A paragraph for the main content.</p>
  <p>And another one.</p>
  <template #footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

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

<!-- 这样会触发一个警告 -->
<current-user #="{ user }">
  {{ user.firstName }}
</current-user>

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

<current-user #default="{ user }">
  {{ user.firstName }}
</current-user>


其它範例


插槽prop 允許我們將插槽轉換為可重複使用的模板,這些模板可以基於輸入的prop 渲染出不同的內容。 這在設計封裝資料邏輯同時允許父級元件自訂部分佈局的可重複使用元件時是最有用的。

例如,我們要實作一個

<todo-list> 元件,它是一個清單且包含佈局和過濾邏輯:

<ul>
  <li
    v-for="todo in filteredTodos"
    v-bind:key="todo.id"
  >
    {{ todo.text }}
  </li>
</ul>

我們可以將每個todo作為父級元件的插槽,以此透過父級元件對其進行控制,然後將

todo 作為一個插槽prop 進行綁定:

<ul>
  <li
    v-for="todo in filteredTodos"
    v-bind:key="todo.id"
  >
    <!--
    我们为每个 todo 准备了一个插槽,
    将 `todo` 对象作为一个插槽的 prop 传入。
    -->
    <slot name="todo" v-bind:todo="todo">
      <!-- 后备内容 -->
      {{ todo.text }}
    </slot>
  </li>
</ul>

現在當我們使用

<todo-list> 元件的時候,我們可以選擇為todo 定義一個不一樣的<template> 作為替代方案,並且可以從子元件取得資料:

<todo-list v-bind:todos="todos">
  <template v-slot:todo="{ todo }">
    <span v-if="todo.isComplete">?</span>
    {{ todo.text }}
  </template>
</todo-list>

這只是作用域插槽用武之地的冰山一角。想了解更多現實生活中的作用域插槽的用法,我們推薦瀏覽諸如 Vue Virtual ScrollerVue PromisedPortal Vue 等函式庫。


廢棄了的語法


##v -slot 指令自 Vue 2.6.0 起被引入,提供更好的支援 slot 和 slot-scope 特性的 API 替代方案。 v-slot 完整的由來參見這份 RFC。在接下來所有的 2.x 版本中 slot 和 slot-scope 特性仍會被支持,但已經被官方廢棄且不會出現在 Vue 3 中。


slot 特性的具名插槽

自 2.6.0 起被廢棄。新推薦的語法請查閱這裡

<template> 上使用特殊的slot 特性,可以將內容從父級傳給具名插槽(把這裡提到過的<base-layout> 元件當作範例):

<base-layout>
  <template 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 slot="footer">
    <p>Here's some contact info</p>
  </template>
</base-layout>

或直接把

slot 特性用在一個普通元素上:

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

這裡其實還有一個未命名插槽,也就是

預設插槽,捕捉所有未符合的內容。上述兩個範例的HTML 渲染結果皆為:

<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>


#搭配slot-scope

搭配

slot-scope 特性的作用域插槽

從 2.6.0 起廢棄。新推薦的文法請查閱這裡<template> 上使用特殊的

slot-scope### 特性,可以接收傳遞給插槽的prop (把###這裡# ##提到的###<slot-example>### 元件作為範例):###
<slot-example>
  <template slot="default" slot-scope="slotProps">
    {{ slotProps.msg }}
  </template>
</slot-example>

這裡的 slot-scope 宣告了被接收的 prop 物件會作為 slotProps 變數存在於 <template> 作用域中。你可以像命名 JavaScript 函數參數一樣隨意命名 slotProps

這裡的slot="default" 可以被忽略為隱性寫法:

<slot-example>
  <template slot-scope="slotProps">
    {{ slotProps.msg }}
  </template>
</slot-example>

slot-scope 特性也可以直接用於非<template> 元素(包括元件):

<slot-example>
  <span slot-scope="slotProps">
    {{ slotProps.msg }}
  </span>
</slot-example>

slot-scope 的值可以接收任何有效的可以出現在函數定義的參數位置上的JavaScript表達式。這意味著在支援的環境下(單一檔案元件現代瀏覽器),你也可以在表達式中使用ES2015 解構,如下:

<slot-example>
  <span slot-scope="{ msg }">
    {{ msg }}
  </span>
</slot-example>

使用這裡描述過的<todo-list> 作為範例,與它等價的使用slot-scope 的程式碼是:

<todo-list v-bind:todos="todos">
  <template slot="todo" slot-scope="{ todo }">
    <span v-if="todo.isComplete">?</span>
    {{ todo.text }}
  </template>
</todo-list>


#