插槽
插槽:簡單理解就是組件內(nèi)部留一個或多個的插槽位置,可供組件傳對應(yīng)的模板代碼進去。插槽的出現(xiàn),讓組件變的更加靈活。
該頁面假設(shè)你已經(jīng)閱讀過了組件基礎(chǔ)。如果你還對組件不太了解,推薦你先閱讀它。
在 2.6.0 中,我們?yōu)榫呙宀酆妥饔糜虿宀垡肓艘粋€新的統(tǒng)一的語法 (即
v-slot
指令)。它取代了slot
和slot-scope
這兩個目前已被廢棄但未被移除且仍在文檔中的特性。新語法的由來可查閱這份 RFC。
目錄
插槽內(nèi)容
Vue 實現(xiàn)了一套內(nèi)容分發(fā)的 API,這套 API 的設(shè)計靈感源自 Web Components 規(guī)范草案,將 <slot>
元素作為承載分發(fā)內(nèi)容的出口。
它允許你像這樣合成組件:
<navigation-link url="/profile"> Your Profile </navigation-link>
然后你在 <navigation-link>
的模板中可能會寫為:
<a v-bind:href="url" class="nav-link" > <slot></slot> </a>
當(dāng)組件渲染的時候,<slot></slot>
將會被替換為“Your Profile”。插槽內(nèi)可以包含任何模板代碼,包括 HTML:
<navigation-link url="/profile"> <!-- 添加一個 Font Awesome 圖標(biāo) --> <span class="fa fa-user"></span> Your Profile </navigation-link>
甚至其它的組件:
<navigation-link url="/profile"> <!-- 添加一個圖標(biāo)的組件 --> <font-awesome-icon name="user"></font-awesome-icon> Your Profile </navigation-link>
如果 <navigation-link>
沒有包含一個 <slot>
元素,則該組件起始標(biāo)簽和結(jié)束標(biāo)簽之間的任何內(nèi)容都會被拋棄。
編譯作用域
當(dāng)你想在一個插槽中使用數(shù)據(jù)時,例如:
<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> 組件*內(nèi)部*定義的。 --> </navigation-link>
作為一條規(guī)則,請記?。?/p>
父級模板里的所有內(nèi)容都是在父級作用域中編譯的;子模板里的所有內(nèi)容都是在子作用域中編譯的。
后備內(nèi)容
有時為一個插槽設(shè)置具體的后備 (也就是默認(rèn)的) 內(nèi)容是很有用的,它只會在沒有提供內(nèi)容的時候被渲染。例如在一個 <submit-button>
組件中:
<button type="submit"> <slot></slot> </button>
我們可能希望這個 <button>
內(nèi)絕大多數(shù)情況下都渲染文本“Submit”。為了將“Submit”作為后備內(nèi)容,我們可以將它放在 <slot>
標(biāo)簽內(nèi):
<button type="submit"> <slot>Submit</slot> </button>
現(xiàn)在當(dāng)我在一個父級組件中使用 <submit-button>
并且不提供任何插槽內(nèi)容時:
<submit-button></submit-button>
后備內(nèi)容“Submit”將會被渲染:
<button type="submit"> Submit </button>
但是如果我們提供內(nèi)容:
<submit-button> Save </submit-button>
則這個提供的內(nèi)容將會被渲染從而取代后備內(nèi)容:
<button type="submit"> Save </button>
具名插槽
自 2.6.0 起有所更新。已廢棄的使用
slot
特性的語法在這里。
有時我們需要多個插槽。例如對于一個帶有如下模板的 <base-layout>
組件:
<div class="container"> <header> <!-- 我們希望把頁頭放這里 --> </header> <main> <!-- 我們希望把主要內(nèi)容放這里 --> </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”。
在向具名插槽提供內(nèi)容的時候,我們可以在一個 <template>
元素上使用 v-slot
指令,并以 v-slot
的參數(shù)的形式提供其名稱:
<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>
現(xiàn)在 <template>
元素中的所有內(nèi)容都將會被傳入相應(yīng)的插槽。任何沒有被包裹在帶有 v-slot
的 <template>
中的內(nèi)容都會被視為默認(rèn)插槽的內(nèi)容。
然而,如果你希望更明確一些,仍然可以在一個 <template>
中包裹默認(rèn)插槽的內(nèi)容:
<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>
上 (只有一種例外情況),這一點和已經(jīng)廢棄的 slot
特性不同。
作用域插槽
自 2.6.0 起有所更新。已廢棄的使用
slot-scope
特性的語法在這里。
有時讓插槽內(nèi)容能夠訪問子組件中才有的數(shù)據(jù)是很有用的。例如,設(shè)想一個帶有如下模板的 <current-user>
組件:
<span> <slot>{{ user.lastName }}</slot> </span>
我們想讓它的后備內(nèi)容顯示用戶的名,以取代正常情況下用戶的姓,如下:
<current-user> {{ user.firstName }} </current-user>
然而上述代碼不會正常工作,因為只有 <current-user>
組件可以訪問到 user
而我們提供的內(nèi)容是在父級渲染的。
為了讓 user
在父級的插槽內(nèi)容中可用,我們可以將 user
作為 <slot>
元素的一個特性綁定上去:
<span> <slot v-bind:user="user"> {{ user.lastName }} </slot> </span>
綁定在 <slot>
元素上的特性被稱為插槽 prop?,F(xiàn)在在父級作用域中,我們可以給 v-slot
帶一個值來定義我們提供的插槽 prop 的名字:
<current-user> <template v-slot:default="slotProps"> {{ slotProps.user.firstName }} </template> </current-user>
在這個例子中,我們選擇將包含所有插槽 prop 的對象命名為 slotProps
,但你也可以使用任意你喜歡的名字。
獨占默認(rèn)插槽的縮寫語法
在上述情況下,當(dāng)被提供的內(nèi)容只有默認(rèn)插槽時,組件的標(biāo)簽才可以被當(dāng)作插槽的模板來使用。這樣我們就可以把 v-slot
直接用在組件上:
<current-user v-slot:default="slotProps"> {{ slotProps.user.firstName }} </current-user>
這種寫法還可以更簡單。就像假定未指明的內(nèi)容對應(yīng)默認(rèn)插槽一樣,不帶參數(shù)的 v-slot
被假定對應(yīng)默認(rèn)插槽:
<current-user v-slot="slotProps"> {{ slotProps.user.firstName }} </current-user>
注意默認(rèn)插槽的縮寫語法不能和具名插槽混用,因為它會導(dǎo)致作用域不明確:
<!-- 無效,會導(dǎo)致警告 --> <current-user v-slot="slotProps"> {{ slotProps.user.firstName }} <template v-slot:other="otherSlotProps"> slotProps is NOT available here </template> </current-user>
只要出現(xiàn)多個插槽,請始終為所有的插槽使用完整的基于 <template>
的語法:
<current-user> <template v-slot:default="slotProps"> {{ slotProps.user.firstName }} </template> <template v-slot:other="otherSlotProps"> ... </template> </current-user>
解構(gòu)插槽 Prop
作用域插槽的內(nèi)部工作原理是將你的插槽內(nèi)容包括在一個傳入單個參數(shù)的函數(shù)里:
function (slotProps) { // 插槽內(nèi)容 }
這意味著 v-slot
的值實際上可以是任何能夠作為函數(shù)定義中的參數(shù)的 JavaScript 表達式。所以在支持的環(huán)境下 (單文件組件或現(xiàn)代瀏覽器),你也可以使用 ES2015
解構(gòu)來傳入具體的插槽 prop,如下:
<current-user v-slot="{ user }"> {{ user.firstName }} </current-user>
這樣可以使模板更簡潔,尤其是在該插槽提供了多個 prop 的時候。它同樣開啟了 prop 重命名等其它可能,例如將 user
重命名為 person
:
<current-user v-slot="{ user: person }"> {{ person.firstName }} </current-user>
你甚至可以定義后備內(nèi)容,用于插槽 prop 是 undefined 的情形:
<current-user v-slot="{ user = { firstName: 'Guest' } }"> {{ user.firstName }} </current-user>
動態(tài)插槽名
2.6.0 新增
動態(tài)指令參數(shù)也可以用在 v-slot
上,來定義動態(tài)的插槽名:
<base-layout> <template v-slot:[dynamicSlotName]> ... </template> </base-layout>
具名插槽的縮寫
2.6.0 新增
跟 v-on
和 v-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> <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>
然而,和其它指令一樣,該縮寫只在其有參數(shù)的時候才可用。這意味著以下語法是無效的:
<!-- 這樣會觸發(fā)一個警告 --> <current-user #="{ user }"> {{ user.firstName }} </current-user>
如果你希望使用縮寫的話,你必須始終以明確插槽名取而代之:
<current-user #default="{ user }"> {{ user.firstName }} </current-user>
其它示例
插槽 prop 允許我們將插槽轉(zhuǎn)換為可復(fù)用的模板,這些模板可以基于輸入的 prop 渲染出不同的內(nèi)容。這在設(shè)計封裝數(shù)據(jù)邏輯同時允許父級組件自定義部分布局的可復(fù)用組件時是最有用的。
例如,我們要實現(xiàn)一個 <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" > <!-- 我們?yōu)槊總€ todo 準(zhǔn)備了一個插槽, 將 `todo` 對象作為一個插槽的 prop 傳入。 --> <slot name="todo" v-bind:todo="todo"> <!-- 后備內(nèi)容 --> {{ todo.text }} </slot> </li> </ul>
現(xiàn)在當(dāng)我們使用 <todo-list>
組件的時候,我們可以選擇為 todo 定義一個不一樣的 <template>
作為替代方案,并且可以從子組件獲取數(shù)據(jù):
<todo-list v-bind:todos="todos"> <template v-slot:todo="{ todo }"> <span v-if="todo.isComplete">?</span> {{ todo.text }} </template> </todo-list>
這只是作用域插槽用武之地的冰山一角。想了解更多現(xiàn)實生活中的作用域插槽的用法,我們推薦瀏覽諸如 Vue Virtual Scroller、Vue Promised 和 Portal Vue 等庫。
廢棄了的語法
v-slot
指令自 Vue 2.6.0 起被引入,提供更好的支持slot
和slot-scope
特性的 API 替代方案。v-slot
完整的由來參見這份 RFC。在接下來所有的 2.x 版本中slot
和slot-scope
特性仍會被支持,但已經(jīng)被官方廢棄且不會出現(xiàn)在 Vue 3 中。
帶有 slot
特性的具名插槽
自 2.6.0 起被廢棄。新推薦的語法請查閱這里。
在 <template>
上使用特殊的 slot
特性,可以將內(nèi)容從父級傳給具名插槽 (把這里提到過的 <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>
這里其實還有一個未命名插槽,也就是默認(rèn)插槽,捕獲所有未被匹配的內(nèi)容。上述兩個示例的 HTML 渲染結(jié)果均為:
<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
特性的作用域插槽
自 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 函數(shù)參數(shù)一樣隨意命名 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
的值可以接收任何有效的可以出現(xiàn)在函數(shù)定義的參數(shù)位置上的 JavaScript 表達式。這意味著在支持的環(huán)境下 (單文件組件或現(xiàn)代瀏覽器),你也可以在表達式中使用 ES2015
解構(gòu),如下:
<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>