Vue 插槽(slot)使用(通俗易懂)

YXi發表於2019-08-20

因為在2.6.0中,具名插槽作用域插槽 引入了一個新的統一的語法 (即v-slot 指令)。它取代了 slotslot-scope,並且現在網上都說的是一些老版本的內容,官方文件不太容易理解,所以就整理了一篇有關於插槽(slot)使用的文章


Slot 通俗的理解就是“佔坑”,在元件模板中佔好了位置,當使用該元件標籤時候,元件標籤裡面的內容就會自動填坑(替換元件模板中slot位置)
並且可以作為承載分發內容的出口

內容插槽

定義兩個元件 home.vuetest.vue
然後在home.vue元件中引用test.vue元件

插槽內可以包含普通文字

//home.vue
<test>
     Hello Word
</test>
複製程式碼
//test.vue
<a href="#">
	 <slot></slot>
</a>
複製程式碼

當元件渲染的時候,<slot></slot>會被替換為Hello Word

插槽內也可以包含任何模板程式碼,包括HTML

在你的index.html引入Font Awesome圖示的樣式就直接可以用那裡面的圖示了
<link href="//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">

//home.vue
<test>
    <!-- 新增一個 Font Awesome 圖示 -->
    <span class="fa fa-user"></span>
    Hello Word
</test>
複製程式碼

插槽內新增其他元件

//home.vue
<test>
    <!-- 新增一個圖示的元件 -->
    <font-awesome-icon></font-awesome-icon>
    Hello Word
</test>
複製程式碼

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

在插槽中使用資料

插槽跟模板其他地方一樣都可以訪問相同的例項屬性(也就是相同的"作用域"),而不能訪問<test>的作用域

//home.vue
<test>
	//插槽可以獲取到home元件裡的內容
	Hello {{enhavo}}
</test>

data(){
	return{
		enhavo:'word'
	}
}
複製程式碼
//home.vue
//這裡是獲取不到name的,因為這個值是傳給<test>的
<test name='you'>
    Hello {{name}}
</test>
複製程式碼

規則:
父級模板裡的所有內容都是在父級作用域中編譯的;子模板裡的所有內容都是在子作用域中編譯的。

後備內容(預設內容)插槽

有時候我們需要給插槽設定一個具體的預設內容,當別的元件沒有給你內容的時候,那麼預設的內容就會被渲染

//test.vue
//在slot插槽裡設定預設內容 Submit
<button>
  <slot>Submit</slot>
</button>
複製程式碼

home.vue裡直接使用test.vue如下:

//home.vue
<test></test>
複製程式碼

那麼最後設定的預設內容 Submit 將會被渲染

<button>
   Submit
</button>
複製程式碼

假如我們提供內容呢?

//home.vue
<test>按鈕</test>
複製程式碼

那麼這個提供的內容將會替代預設的內容被渲染出來

<button>
   按鈕
</button>
複製程式碼

具名插槽

有時候我們一個元件裡需要多個插槽

那麼怎麼辦呢? 對於這樣的情況,<slot>元素有一個特殊的特性:name ,這個特性可以用來定義額外的插槽

<div>
  <header>
    <!-- 我們希望把頁頭放這裡 -->
  </header>
  
  <main>
    <!-- 我們希望把主要內容放這裡 -->
  </main>
  
  <footer>
    <!-- 我們希望把頁尾放這裡 -->
  </footer>
</div>
複製程式碼

這時候,我們就可以使用name屬性

<div>
  <header>
    <slot name="header"></slot>
  </header>
  
  <main>
    <slot></slot>
  </main>
  
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
複製程式碼

如果一個<slot>不帶name屬性的話,那麼它的name預設為default
在向具名插槽提供內容的時候,我們可以在<template>元素上使用v-slot指令,並以引數的形式提供其名稱

<div>
   <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 some contact info</p>
  </template>
</div>
複製程式碼

現在 <template> 元素中的所有內容都將會被傳入相應的插槽。任何沒有被包裹在帶有 v-slot<template> 中的內容都會被視為預設插槽的內容。

如果你希望更明確一點的話,那就把主體內容那個插槽裡設定name="default",然後把上面的內容包裹起來

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

注:v-slot只能新增在一個<template>上,(只有一種例外情況,下面會說)

作用域插槽

上面已經說了,插槽跟模板其他地方一樣都可以訪問相同的例項屬性(也就是相同的"作用域"),而不能訪問<test>的作用域

那如果想訪問<test>作用域該怎麼辦呢?
我們把需要傳遞的內容綁到 <slot> 上,然後在父元件中用v-slot設定一個值來定義我們提供插槽的名字:

//test.vue
<div>
	<!-- 設定預設值:{{user.lastName}}獲取 Jun -->
	<!-- 如果home.vue中給這個插槽值的話,則不顯示 Jun -->
	<!-- 設定一個 usertext 然後把user綁到設定的 usertext 上 -->
	<slot v-bind:usertext="user">{{user.lastName}}</slot>
</div>

//定義內容
data(){
  return{
	user:{
	  firstName:"Fan",
	  lastName:"Jun"
	}
  }
}
複製程式碼

然後在home.vue中接收傳過來的值:

//home.vue
<div>
  <test v-slot:default="slotProps">
    {{slotProps.usertext.firstName}}
  </test>
</div>
複製程式碼

這樣就可以獲得test.vue元件傳過來的值了

繫結在 <slot> 元素上的特性被稱為插槽 prop。在父元件中,我們可以用 v-slot 設定一個值來定義我們提供的插槽 prop 的名字,然後直接使用就好了

獨佔預設插槽的縮寫語法

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

這樣寫法還可以更簡單,因為不帶引數的v-slot就被假定為預設插槽,所以上面的程式碼還可以簡化:

<div>
  <!-- 可以把 :default 去掉,僅限於預設插槽 -->
  <test v-slot="slotProps">
    {{slotProps.usertext.firstName}}
  </test>
</div>
複製程式碼

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

<div>
  <!-- 可以把 :default 去掉,僅限於預設插槽 -->
  <test v-slot="slotProps">
    {{slotProps.usertext.firstName}}
    <!-- 無效,會警告 -->
    <template v-slot:other="otherSlotProps">
      slotProps is NOT available here
    </template>
  </test>
</div>
複製程式碼

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

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

  <template v-slot:other="otherSlotProps">
    ...
  </template>
</test>
複製程式碼

解構插槽Prop

因為 作用域插槽 的內部工作原理是將你的插槽內容包括在一個傳入單個引數的函式裡
這意味著 v-slot 的值實際上可以是任何能夠作為函式定義中的引數的 JS 表示式

所以本來是這樣寫的:

<div>
  <test v-slot="slotProps">
    {{slotProps.usertext.firstName}}
  </test>
</div>
複製程式碼

還可以這樣寫:

<div>
  <test v-slot={usertext}>
    {{usertext.firstName}}
  </test>
</div>
複製程式碼

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

例如可以將 usertext 重新命名為 person:

<div>
  <test v-slot={usertext:person}>
    {{person.firstName}}
  </test>
</div>
複製程式碼

甚至可以定義 後備內容(預設內容),用於插槽沒有值時可以使用預設內容的情形:

<div>
  <test v-slot="{usertext={firstName:'Yang'}}">
    {{usertext.firstName}}
  </test>
</div>
複製程式碼

動態插槽名(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

原來是這樣寫的:

<div>
   <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 some contact info</p>
  </template>
</div>  
複製程式碼

現在可以這樣寫:

<div>
   <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 some contact info</p>
  </template>
</div>
複製程式碼

注:該指令和其他指令一樣,只在其有引數的時候才可用

下面的書寫形式是錯誤的:

<test #="{ usertext }">
  {{ usertext.firstName }}
</test>
複製程式碼

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

<test #default="{ usertext }">
  {{ usertext.firstName }}
</test>
複製程式碼

其他示例

插槽 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>
複製程式碼

至於那些廢棄了的 slotslot-scope 特性,這裡就不在闡述了,如果有興趣瞭解的話,請參考官方文件

拜拜

@_@

相關文章