詳解Vue中的插槽
- 2020 年 12 月 29 日
- 筆記
作者: 小土豆
部落格園://www.cnblogs.com/HouJiao/
掘金://juejin.im/user/2436173500265335
什麼是插槽
在日常的項目開發中,當我們在編寫一個完整的組件時,不可避免的會引用一些外部組件
或者自定義組件
。
有了這種引用關係
之後,我們就可以把它們稱為父組件
或者子組件
,同時父子組件
之間有很多的通訊方式,比如可以通過props
向子組件
傳遞數據,或者通過$emit
、$parent
調用父組件
中的方法。
下面就是一個非常簡單的父組件
引用子組件
的例子。
<!-- 子組件: /slot-demo/src/components/Child.vue -->
<template>
<div class="child">
<!-- 標記 -->
<div>
<i class="el-icon-s-flag"></i>Badge 標記
<div class="content">
<el-badge :value="12" class="item">
<el-button size="small">評論</el-button>
</el-badge>
</div>
</div>
<!-- 進度條 -->
<div>
<i class="el-icon-s-flag"></i>進度條
<div class="content">
<el-progress :percentage="50"></el-progress>
</div>
</div>
</div>
</template>
<!-- 省略其它程式碼 -->
接著我們在App
組件中引用Child
組件。
<!-- 父組件: /slot-demo/src/App.vue -->
<template>
<div id="app">
<!-- 使用子組件 -->
<child></child>
</div>
</template>
<script>
import Child from './components/Child.vue'
export default {
name: 'App',
components: {
Child
}
}
</script>
最後運行項目,子組件
的內容成功被引用並展示在頁面上。
那假如我們現在有這樣一個需求:在引用Child
組件的同時,希望在Child
組件的指定位置
插入一段內容:<h1> 歡迎大家關注小土豆 </h1>
。
如果我們直接將內容寫入<child></child>
內部,是不會生效的。
<!-- 父組件: /slot-demo/src/App.vue -->
<template>
<div id="app">
<!-- 使用子組件 -->
<child>
<h1> 歡迎大家關注小土豆 </h1>
</child>
</div>
</template>
<script>
import Child from './components/Child.vue'
export default {
name: 'App',
components: {
Child
}
}
</script>
可以看到並未達到預期效果:
那為了解決類似這樣的問題,Vue
就設計出來了slot
這個東西。slot
翻譯過來叫做插槽
,也可稱其為Vue
的內容分發機制,它的主要作用就是向子組件
的指定位置
插入一段內容,這個內容可以是HTML
或者其他的組件
。
默認插槽
在前面一節內容里,我們提出了一個需求:在引用Child
組件的同時,希望在Child
組件的指定位置
插入一段內容:<h1> 歡迎大家關注小土豆 </h1>
。
那這個需求如何使用插槽
來實現呢?我們來實踐一下。
首先我們需要在子組件
中寫入<slot></slot>
,同時這個在<slot>
標籤內部可以有默認的內容,比如<slot>我是這個slot裡面本來的內容</slot>
<!-- 子組件: /src/components/Child.vue -->
<template>
<div class="child">
<!-- 標記 -->
<div>
<i class="el-icon-s-flag"></i>Badge 標記
<div class="content">
<el-badge :value="12" class="item">
<el-button size="small">評論</el-button>
</el-badge>
</div>
</div>
<!-- 進度條 -->
<div>
<i class="el-icon-s-flag"></i>進度條
<div class="content">
<el-progress :percentage="50"></el-progress>
</div>
</div>
<!-- 佔位符 -->
<slot>我是這個slot裡面本來的內容</slot>
</div>
</template>
<!-- 省略其它程式碼 -->
接著就是在父組件
中傳入我們希望插入到子組件
中的內容。
<!-- 父組件: /src/App.vue -->
<template>
<div id="app">
<!-- 使用子組件 -->
<child>
<h1> 歡迎大家關注小土豆 </h1>
</child>
</div>
</template>
<script>
import Child from './components/Child.vue'
export default {
name: 'App',
components: {
Child
}
}
</script>
此時在運行項目,就能看到<h1> 歡迎大家關注小土豆 </h1>
這段內容已經成功的顯示在頁面上。
具名插槽
具名插槽
就是給我們的插槽
起一個名字,即給<slot></slot>
定義一個name
屬性。
<!-- 插槽名稱為:heading -->
<slot name="heading"></slot>
<!-- 插槽名稱為:sub-heading -->
<slot name="sub-heading"></slot>
<!-- 插槽名稱為:footer-text -->
<slot name="footer-text"></slot>
給插槽
起了名稱以後,我們在父組件
中就可以使用v-slot:name
或者#name
往指定的插槽
填充內容。
#name
是v-slot:name
的簡寫形式
下面我們就來實踐一下具名插槽
。
首先是在子組件(Child.vue)
中定義具名插槽
。
<!-- 子組件: /slot-demo/src/components/Child.vue -->
<template>
<div class="child">
<!-- 插槽名稱為:heading -->
<slot name="heading"></slot>
<!-- 插槽名稱為:sub-heading -->
<slot name="sub-heading"></slot>
<!-- 標記 -->
<div>
<i class="el-icon-s-flag"></i>Badge 標記
<div class="content">
<el-badge :value="12" class="item">
<el-button size="small">評論</el-button>
</el-badge>
</div>
</div>
<!-- 進度條 -->
<div>
<i class="el-icon-s-flag"></i>進度條
<div class="content">
<el-progress :percentage="50"></el-progress>
</div>
</div>
<!-- 插槽名稱為:footer-text -->
<slot name="footer-text"></slot>
</div>
</template>
<!-- 省略其它程式碼 -->
接著在父組件(App.vue)
中使用。
<!-- 父組件: /slot-demo/src/App.vue -->
<template>
<div id="app">
<!-- 使用子組件 -->
<child>
<template v-slot:heading>
<h1>element-ui組件</h1>
</template>
<template v-slot:sub-heading>
<p>這裡是element-ui的部分組件介紹</p>
</template>
<template v-slot:footer-text>
<p>出品@小土豆</p>
</template>
</child>
</div>
</template>
<script>
import Child from './components/Child.vue'
export default {
name: 'App',
components: {
Child
}
}
</script>
運行項目就能看到對應的內容被插入到對應的插槽內:
補充內容——默認插槽的name
屬性
其實關於前面的默認插槽
它也是有name
屬性的,其值為default
,所以在父組件
中也可以這樣寫:
<!-- 父組件: /slot-demo/src/App.vue -->
<child>
<template v-slot:defalut>
<h1> 歡迎大家關注小土豆 </h1>
</template>
</child>
補充內容——<template>
元素上使用 v-slot
指令
在演示具名插槽
的時候,我們的v-slot
是寫在<template>
元素上的,這個是比較推薦的寫法,因為<template>
在處理的過程中不會渲染成真實的DOM
節點。
<template v-slot="default">
<h1>歡迎關注小土豆</h1>
</template>
處理之後的DOM
節點:
<h1 data-v-2dcc19c8="">歡迎關注小土豆</h1>
當然我們也可以將v-slot
應用在其他的HTML
元素上,這樣最終插入到子組件中的內容就會有一層真實的DOM
節點包裹。
<div class="text" v-slot="default">
<h1>歡迎關注小土豆</h1>
</div>
處理之後的DOM
節點:
<div data-v-2dcc19c8="" class="text">
<h1 data-v-2dcc19c8="">歡迎關注小土豆</h1>
</div>
作用域插槽
關於作用域插槽
的相關概念和示例看了很多,但相對於前面兩種類型的插槽來說,確實有些難以理解。如果需要用一句話去總結作用域插槽
,那就是在父組件中訪問子組件的數據
,或者從數據流向
的角度來講就是將子組件的數據傳遞到父組件
。
一個新概念或者一個新技術的出現總是有原因的,那作用域插槽
的出現又是為了解決什麼樣的問題呢?一起來研究一下吧。
作用域插槽的使用
我們先來看看如何利用作用域插槽
實現在父組件中訪問子組件的數據
。
首先我們需要在子組件
的插槽<slot><slot>
上使用v-bind
綁定對應的數據。
<!-- 子組件: /slot-demo/src/components/Child.vue -->
<template>
<div class="child">
<slot
name="heading"
v-bind:headingValue="heading">
{{heading}}
</slot>
<!-- 為了讓大家看的更清楚 已經將Child.vue組件中多餘的內容刪除 -->
</div>
</template>
<script>
export default {
name: 'Child',
data() {
return {
heading: '這裡是默認的heading'
}
}
}
</script>
可以看到我們在<slot>
上使用v-bind
綁定了vue data
中定義的heading
數據。
接著我們就可以在父組件
中定義一個變數
來接收子組件
中傳遞的數據。
父組件中接收數據的
變數名
可以隨意起,這裡我起的變數名為slotValue
<!-- 父組件: /slot-demo/src/App.vue -->
<template>
<div id="app">
<child>
<template v-slot:heading="slotValue" >
<h1>element-ui組件</h1>
slotValue = {{slotValue}}
</template>
</child>
</div>
</template>
運行項目後查看頁面的結果:
可以看到slotValue
是一個對象,保存了一組數據,其鍵
就是我們在子組件
的<slot>
上使用v-bind
綁定的屬性名headingValue
,其值
是v-bind
綁定的heading
值。
作用域插槽的應用場景
前面我們了解了作用域插槽
的用法,也得知其主要目的是為了能在父組件
中訪問子組件
的數據。那什麼時候父組件
需要訪問子組件
的數據呢。
我們來舉個簡單的栗子。
假設我們有下面這樣一個Card
組件:
<!-- Card組件:/slot-demo/src/components/Card.vue -->
<template>
<div class="card">
<h3>{{title}}</h3>
<p v-for="item in list" :key="item.id">
{{item.id}}.{{item.text}}
</p>
</div>
</template>
<script>
export default {
name: 'Card',
props: ['title', 'list'],
data() {
return {
}
}
}
</script>
<style scoped>
.list{
border: 1px solid;
padding: 20px;
}
.list p{
border-bottom: 2px solid #fff;
padding-bottom: 5px;
}
</style>
其中Card
組件中展示的title
和list
數據由父組件
傳入。
接著在App
組件中復用Card
組件,並且傳入title
和list
數據。
<!-- App組件:/slot-demo/src/App.vue -->
<template>
<div id="app">
<card :list="list" :title="title">
</card>
</div>
</template>
<script>
import Card from './components/Card.vue'
export default {
name: 'App',
components: {
Card,
},
data() {
return {
title: '名人名言',
list:[
{
id:1,
text:'要成功,先發瘋,頭腦簡單向前沖'
},{
id:2,
text:'不能天生麗質就只能天生勵志!'
},{
id:3,
text:'世上唯一不能複製的是時間,唯一不能重演的是人生。'
}
]
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* text-align: center; */
/* color: #2c3e50; */
/* margin-top: 60px; */
color: #fff;
background: rgba(232, 0, 0, 0.3);
padding: 20px;
}
</style>
運行項目查看頁面:
Card
組件本身並不複雜,就是展示title
和list
裡面的數據。但凡是有相同需求的都可以通過復用Card
組件來實現。
但是仔細去想,我們的Card
組件其實並沒有那麼靈活:如果有些頁面需要復用
該組件,但是希望在title
處增加一個圖標
;或者有些頁面需要在展示內容時候不顯示編號1、2、3
。
那這樣的需求使用插槽
就可以輕鬆實現。
<!-- Card組件:/slot-demo/src/components/Card.vue -->
<template>
<div class="card">
<h3>
<slot name="title" v-bind:titleValue="title"> {{title}} </slot>
</h3>
<p v-for="item in list" :key="item.id">
<slot name="text" v-bind:itemValue="item">{{item.id}}.{{item.text}}</slot>
</p>
</div>
</template>
我們在Card
組件展示title
和list
的位置分別添加了對應的具名插槽
,並且通過v-bind
將title
、item
(list
循環出來的數據)傳遞給了父組件
。
此時父組件
就可以控制子組件
的顯示。
假如我們需要在title
處添加圖標,則App
組件復用Card
組件的方式如下:
<!-- App組件:/slot-demo/src/App.vue -->
<card :list="list" :title="title">
<template v-slot:title="slotTitle">
<i class="el-icon-guide"></i>{{slotTitle.titleValue}}
</template>
</card>
頁面效果:
亦或者有些頁面需要在展示內容時候不顯示編號1、2、3
:
<!-- App組件:/slot-demo/src/App.vue -->
<card :list="list" :title="title">
<template v-slot:text="slotItem">
{{slotItem.itemValue.text}}
</template>
</card>
頁面效果:
這裡應該能想起來
element table
組件的實現方式,是不是也有點這樣的意思呢
到這裡或許有人會說這樣的需求不用插槽也能實現,直接在Card
組件中增加一些邏輯即可。這樣的說法固然是可以實現功能,但是顯然不是一個好辦法。
因為組件的設計本身是希望拿來複用的,如果這個組件本身大部分實現是符合我們的需求的,只有一小部分不符合,我們首先應該想要的是去擴展該組件
,而不是修改組件
,這也是軟體設計的思想:開放擴展,關閉修改
。所以插槽
的出現正是對組件的一種擴展,讓我們可以更加靈活的復用組件。
廢棄的插槽語法
關於以上所描述的插槽
語法,均是vue 2.6.0
以後的語法。在這之前,插槽的語法為slot(默認插槽或者具名插槽)
和slot-scope(作用域插槽)
。
默認插槽
<!-- Card組件:/slot-demo/src/components/Card.vue -->
<!-- 子組件的寫法依然不變 -->
<slot></slot>
<!-- App組件:/slot-demo/src/App.vue -->
<child>
<template>
<h1>歡迎關注小土豆</h1>
</template>
<child>
<p>或者</p>
<child>
<template slot="default">
<h1>歡迎關注小土豆</h1>
</template>
<child>
頁面效果:
具名插槽
<!-- Card組件:/slot-demo/src/components/Card.vue -->
<!-- 子組件的寫法依然不變 -->
<!-- 插槽名稱為:heading -->
<slot name="heading"></slot>
<!-- 插槽名稱為:sub-heading -->
<slot name="sub-heading"></slot>
<!-- 插槽名稱為:footer-text -->
<slot name="footer-text"></slot>
<!-- App組件:/slot-demo/src/App.vue -->
<child>
<template slot="heading">
<h1>element-ui組件</h1>
</template>
<template slot="sub-heading">
<p>這裡是element-ui的部分組件介紹</p>
</template>
<template slot="footer-text">
<p>出品@小土豆</p>
</template>
</child>
頁面效果:
作用域插槽
<!-- 子組件: /src/components/Child.vue -->
<slot
name="heading"
v-bind:headingValue="heading">
{{heading}}
</slot>
<!-- 父組件: /slot-demo/src/App.vue -->
<child>
<template slot="heading" slot-scope="headingValue" >
<h1>element-ui組件</h1>
headingValue = {{headingValue}}
</template>
</child>
頁面效果:
總結
到這裡本篇文章就結束了,內容非常簡單易懂,可以是茶餘飯後的一篇知識回顧。
最後我們在來做一個小小的總結:
近期文章
骨架屏(page-skeleton-webpack-plugin)初探
Vue結合Django-Rest-Frameword實現登錄認證(二)
Vue結合Django-Rest-Frameword實現登錄認證(一)
寫在最後
如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者
文章公眾號
首發,關注 不知名寶藏程式媛
第一時間獲取最新的文章
筆芯❤️~