iT邦幫忙

2021 iThome 鐵人賽

DAY 24
0
Modern Web

排版神器 Tailwind CSS~和兔兔一起快速上手漂亮的元件開發!系列 第 25

Day 24:「Switch 也要換遊戲片啦~」- Slot 插槽

Day24-Banner-not-yet

不知道大家有沒有買 Switch 呢?

雖然兔兔沒有 Switch,
但我知道有遊戲片都不便宜呀!

然後,遊戲總會有玩膩的時候
這時候就要把記憶卡插槽裡的卡片拿出來,
接著插一張新的進去。

不過其實,
你不知道有沒有發現,
我們自定義的元件都是只有標籤,
都沒在裡面放內容呢?

<Box :number="1" />

<Box :number="2">
</Box>

我們目前都是把所有的資料丟在屬性,

可是其實原生的寫法應該要可以做到這樣

<Box>1</Box>

<Box>2</Box>

但你會發現在自定義的元件之中這樣用是沒反應的

WHY?
我們接下來就來談談為什麼!
 

carrotPoint 插槽 Slot

如果要像前面所說,想要可以跟原生的寫法一樣能夠在裡面加上內容的話,Slot 就是你要找的功能!

不過我們就要解釋一下為何得靠其他功能來解決,而不能直接放在裡面。

讓我們先看看元件實際上的意義:

<!-- 看起來的樣子 -->
<Menu />

<!-- 實際上的樣子 -->
<ul>
  <li>首頁</li>
  <li>關於</li>
  <li>賣場</li>
  <li>服務</li>
</ul>

沒錯,就是做結構簡化整理

那麼如果今天在元件內加入內容呢?

<!-- 看起來的樣子 -->
<Menu>
  這是我們的選單。
</Menu>

<!-- 實際上的樣子 -->
<ul>
  <li>首頁</li>
  <li>關於</li>
  <li>賣場</li>
  <li>服務</li>
</ul>

沒反應,因為他不知道你要把這些內容實際上是要安排在 template 中的哪裡!

沒錯,就是這麼簡單的問題。

所以 slot 的功能就是要用來告訴 vue 我們要把內容插在實際上結構的哪個地方

而其實 slot 的用法很單純又簡單,
我們只要加上 <slot></slot> 就好。

舉例:

<!-- Menu.vue -->
<template>
  <ul>
    <slot></slot>
    <li v-for="link in links">{{ link }}</li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      links: ["首頁","關於","賣場","服務"]
    }
  }
}
</script>

那麼效果就會是這樣:

<!-- 看起來的樣子 -->
<Menu>
  這是我們的選單。
</Menu>

<!-- 實際上的樣子 -->
<ul>
  這是我們的選單。
  <li>首頁</li>
  <li>關於</li>
  <li>賣場</li>
  <li>服務</li>
</ul>

是不是很輕鬆簡單?

但你可能會納悶:

「目前這個效果用 props 也能做到啊!」

噢,沒錯,你說的對。
不過這樣呢?

<!-- 看起來的樣子 -->
<Menu>
  <div>
    <img src="banner.jpg" />
    <span>歡迎光臨 myfone</span>
  </div>
</Menu>

<!-- 實際上的樣子 -->
<ul>
  <div>
    <img src="banner.jpg" />
    <span>歡迎光臨 myfone</span>
  </div>
  <li>首頁</li>
  <li>關於</li>
  <li>賣場</li>
  <li>服務</li>
</ul>

如果今天要拋入的是元素,是不是就很困難啦?
但是使用 slot 就簡單太多啦!
 

carrotPoint 使用時機

其實應該也沒有很明確的使用時機,
不過多半會用在需要替換內容的元件,比如 modal。

而有時候使用他是為了架構清晰,保持與視覺一致

還有一點我覺得很重要的,
就是如果你要 跨祖孫元件交換資料 的時候,
可以更方便。

因為這麼做等於平坦化,
原本要祖孫元件溝通,現在可以降維成父子元件溝通

舉例:

<!-- 主畫面 -->
<div>
  <List @modify="change($event)" />
</div>

<!-- List.vue -->
<div>
  <div>標題</div>
  <ul>
    <Item v-for="i in 10" @modify="$emit('modify', $event)" />
  </ul>
</div>

<!-- Item.vue -->
<li>
  <input type="text" />
  <button @click="$emit('modify', text)">修改</button>
</li>

像上述這樣的祖孫結構,
資料傳遞起來就很麻煩。

因為你 props 跟 emit 可能都需要連傳兩層
但為何說 slot 可以平坦化、降成父子關係?

看了就明白了:

<!-- 主畫面 -->
<div>
  <List>
    <Item v-for="i in 10" @modify="$emit('modify', $event)" />
  </List>
</div>

<!-- List.vue -->
<div>
  <div>標題</div>
  <ul>
    <slot>
      目前沒有任何文章。
    </slot>
  </ul>
</div>

<!-- Item.vue -->
<li>
  <input type="text" />
  <button @click="$emit('modify', text)">修改</button>
</li>

順帶一提,slot 是可以給予預設內容的,在沒有從外部填入內容之前,可以維持顯示預設內容。

 
這樣我在主畫面之中就可以直接對 Item 元件存取了,
而且結構上看起來可讀性比較高。

不過這種用法還是要經過考慮和評估,
因為如果太濫用,等於所有資料都得跑在父層上
太混雜了,肯定維護起來不容易

(就跟以不好的觀念使用 vuex 一樣,很可怕。)
 

carrotPoint 具名插槽

其實不只可以有一個插槽,也可以有 N 個。

只是,結構裡面就一個區域啊,
怎麼知道我要指定插到哪個插槽?

這時候就是具名插槽該登場的時候啦~

具名插槽也很簡單,就是這樣定義:

<!-- Menu.vue -->
<template>
  <ul>
    <slot name="title"></slot>
    <li v-for="link in links">{{ link }}</li>
  </ul>
  <slot name="description"></slot>
</template>

<script>
export default {
  data() {
    return {
      links: ["首頁","關於","賣場","服務"]
    }
  }
}
</script>

對,簡單的加上 name,然後使用時只要像 v-on:v-bind: 那類的語法一般,加上 v-slot: 以及名稱。

不過必須用 <template></template> 將區塊包裹起來。

所以就會是這樣:

<!-- 使用時看起來的樣子 -->
<Menu>
  <template v-slot:title>
    這是我們的選單。
  </template>
  
  <template v-slot:description>
    選單項目將會不定時更新,敬請期待。
  </template>
</Menu>

<!-- 實際上的樣子 -->
<ul>
  這是我們的選單。
  <li>首頁</li>
  <li>關於</li>
  <li>賣場</li>
  <li>服務</li>
</ul>
選單項目將會不定時更新,敬請期待。

什麼? 你期待這個也有短語法

我說你啊,人不能這麼懶,
而且這種功能要什麼短...

等等,還真的有!

試著用 # 來替代 v-slot 當縮短語法:

<Menu>
  <template #title>
    這是我們的選單。
  </template>
  
  <template #description>
    選單項目將會不定時更新,敬請期待。
  </template>
</Menu>

嘿,丟啦!
這樣就可以了。
 

carrotPoint 插槽 Props

「兔兔! 元件放到插槽之後,就沒辦法吃到前一層元件的變數了欸,那這樣怎麼辦!」

哈哈哈哈哈!
當然不會有這麼不方便的事情存在啊!

其實插槽是有 Props 的!

我們來看看該如何幫插槽設置 props:

<!-- Menu.vue -->
<template>
  <div>
    {{ title }}
  </div>
  <div>
    <ul>
      <slot :list="links"></slot>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: "選單",
      links: ["首頁","關於","賣場","服務"]
    }
  }
}
</script>

就跟一般平時你給 props 的方式一模一樣!

不過存取的方式不一樣:

<Menu v-slot="slotProps">
  <li v-for="item in slotProps.list">
    {{ item }}
  </li>
</Menu>

要透過在 v-slot 後加上 ="slotProps"

slotProps 是 slot 預設的 props 組名稱,傳遞過來的 props 都是 slotProps 下的一個屬性。

當然我們也能解構:

<Menu v-slot="{ list }">
  <li v-for="item in list">
    {{ item }}
  </li>
</Menu>

這樣能存取到上層的變數,是不是又更方便了呢!

(雖然我想不太到使用時機)
 

哇! slot 就這麼介紹了!
其實還有一些更細部的功能啦,
但那些應該要自己去閱讀文件,

我的 vue 篇主要是為了,
把我們之後做元件會用到的功能都先帶過一遍。

那其實 vue 篇就這樣結束啦!
(幫 vue 篇完結灑花)

我們後面接著就要來開始實作元件了!

好期待,不知道大家會不會覺得好玩呢?
 

carrotPoint 給你們的回家作業:

  • 作業實施要點:
    • 複習 Tailwind 篇
    • 複習 Vue 篇
       

關於兔兔們:


 


( # 兔兔小聲說 )

在你們的國度,駕照都是用雞腿換的。

在兔兔這裡,你們知道用的是什麼嗎?

「紅蘿蔔!」

嘖嘖嘖,你以為紅蘿蔔在我這裡這麼好價嘛!
那可是相當於米飯的存在欸!

來,湊近點,我告訴你答案。
其實是:

黃根!!


上一篇
Day 23:「兒子,這是你的零用錢」- 元件間的資料傳遞
下一篇
Day 25:「好慢喔,下載多少了?」- 進度條
系列文
排版神器 Tailwind CSS~和兔兔一起快速上手漂亮的元件開發!32

尚未有邦友留言

立即登入留言