詳解Vue中的插槽

小土豆biubiubiu發表於2020-12-29

作者: 小土豆
部落格園:https://www.cnblogs.com/HouJiao/
掘金:https://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往指定的插槽填充內容。

#namev-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元件中展示的titlelist資料由父元件傳入。

接著在App元件中複用Card元件,並且傳入titlelist資料。

<!-- 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元件本身並不複雜,就是展示titlelist裡面的資料。但凡是有相同需求的都可以通過複用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元件展示titlelist的位置分別新增了對應的具名插槽,並且通過v-bindtitleitemlist迴圈出來的資料)傳遞給了父元件

此時父元件就可以控制子元件的顯示。

假如我們需要在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>

頁面效果:

總結

到這裡本篇文章就結束了,內容非常簡單易懂,可以是茶餘飯後的一篇知識回顧。

最後我們在來做一個小小的總結:

近期文章

記一次真實的Webpack優化經歷

JavaScript的執行上下文,真沒你想的那麼難

骨架屏(page-skeleton-webpack-plugin)初探

Vue結合Django-Rest-Frameword實現登入認證(二)

Vue結合Django-Rest-Frameword實現登入認證(一)

寫在最後

如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者

文章公眾號首發,關注 不知名寶藏程式媛 第一時間獲取最新的文章

筆芯❤️~

相關文章