iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0
Modern Web

從Vue學React!不只要會用,還要真的懂~系列 第 14

【Day 14】設計樣式共用的元件!Vue有v-slot,那React可以怎麼做!?

  • 分享至 

  • xImage
  •  

對vue比較熟悉的朋友們,應該對於slot的使用都不陌生,slot對於增加元件的使用彈性上有著很大的幫助。但是React沒有vue框架中的v-slot,要怎麼彈性的設計一個可以複用的元件呢?今天一樣從Vue開始看,再來看看React的處理方式。

Vue的v-slot是什麼?

https://ithelp.ithome.com.tw/upload/images/20230920/20130914zBPQ1x2RJa.jpg
v-slot的用法我記得我自己在初學vue的時候,也沒有很熟悉,直到真正了解它之後,才知道它的好用之處。其實可以把v-slot的用途當作是在遊戲中,把武器鑲嵌寶石的概念,同一把大刀當鑲嵌了不同寶石後,長相或功能就有可能會不同,但實際上還是基底還是一把大刀。所以v-slot就是一個可以幫助使用者在Vue Template中設計一個可共用的元件的語法,而它的存在就像是把HTML的結構挖出一個個的插槽一樣,會把依照資料有不同顯示的地方挖插槽,等到要使用時,再把對應的資料放入。

在Vue的v-slot用法中,主要會有幾個對應的使用情境。

一般的Slot:當一個共用的元件中,有一個地方需要依照狀況放入資料來顯示的時候。

可以先在子元件中透過slot把會從父層放入資料的地方標示為插槽,這個slot可以留空,也可以如下面範例所示放預設顯示的內容,當使用的父層沒有另外放要顯示什麼時,就會顯示預設的內容。

<template>
  <div class="container">
    <p>
      <!-- 預設顯示內容為default -->
      <slot>default</slot>
    </p>
  </div>
</template>

把有用slot設計的子元件使用在父元件上時,可以透過{{}}把想要放入的state放入,如果只是單純的字串則可以直接放在子元件之中。

<template>
  <div>
    <h2>Vue Slot</h2>
    <!-- 沒有特別放東西到slot裡面時,會顯示預設值 -->
    <SlotComponent />
    <br/>
    <!-- 要放入state,需要使用{{}} -->
    <SlotComponent>{{ content }}</SlotComponent>
    <br/>
    <!-- 放入一般字串直接放在中間即可 -->
    <SlotComponent>一般字串</SlotComponent>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import SlotComponent from './components/SlotComponent.vue';

const content = ref('This is a parent content.');
</script>

實際顯示會如下圖所示,它們共用了一樣的外框和字體顏色,但是內容會因為放入slot的內容而有不同。
https://ithelp.ithome.com.tw/upload/images/20230918/20130914oe7GU7IZDS.png

Named Slot:當一個共用的元件中,需要挖多個插槽,並寫依照資料內容,放入對應位置顯示的時候。

與只有一個插槽的子元件一樣,也需要透過slot把會從父層放入資料的地方標示為插槽,不一樣的地方是為了要能分別不同的slot,需要透過name來個別標記slot

<template>
  <div class="card">
    <h3>
      <slot name="title"></slot>
    </h3>
    <p>
      <!-- 預設顯示內容為default -->
      <slot name="description">default</slot>
    </p>
  </div>
</template>

把有用Named Slot設計的子元件使用在父元件上時,一樣是要把想要放入子元件的內容放在中間,但是因為需要依照不同name的插槽放入對應的內容,所以還需要搭配template使用。

<template>
  <div>
    <h2>Vue Named Slot</h2>
    <NamedSlotComponent v-for="item in cardItemList" :key="item.id">
      <!-- 用template指定特定的slot要放入什麼內容 -->
      <template #title>{{ item.title }}</template>
      <template #description>{{ item.description }}</template>
    </NamedSlotComponent>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import NamedSlotComponent from './components/NamedSlotComponent.vue';

const cardItemList = ref([
  {
    id: 1,
    title: '標題1',
    description: '一些說明1'    
  },
  {
    id: 2,
    title: '標題2',
    description: '一些標題2'
  },
]);
</script>

https://ithelp.ithome.com.tw/upload/images/20230918/20130914i1fNJmYKQD.png

Scoped Slot:當使用有slot的子元件的父元件需要用到子元件內的state來顯示的時候。

在子元件透過v-bind將可能會在使用時帶出來的state綁定上。

<template>
  <div>
    <p>
      <!-- 預設顯示內容為default -->
      <!-- bind一個state在slot -->
      <slot :bind-state="childDiscountState">default</slot>
    </p>
  </div>
</template>

<script setup>
import { ref } from 'vue';
const childDiscountState = ref(0.8);
</script>

把前面這個客製化好的子元件用在父元件中時,就可以透過v-slot把設定在子元件中的data取出

<template>
  <div>
    <h2>Vue Slot</h2>
    <!-- 透過剛剛bind資料時的名稱解構出來使用 -->
    <ScopedSlotComponent v-slot="{ bindState }">
      計算後價格: {{ price * bindState }}
    </ScopedSlotComponent>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import ScopedSlotComponent from './components/ScopedSlotComponent.vue';

const price = ref(200);
</script>

https://ithelp.ithome.com.tw/upload/images/20230918/20130914rRRYfbUBHP.png

這個用法通常會把使用這個元件時,一定會用到的state包裝在這個元件中,等到實際使用時,再依照使用情境去活用這個元件內透過slot綁定的state。例如:使用這個元件一定會帶有8折的計算,就可以透過這樣的方式,把8折帶出來,計算後顯示在畫面上。

Vue有Slot!React有props!

雖然說React並沒有像Vue一樣有slot這樣的用法,但是React還是可以達到相同的效果,靠得就是React的props。昨天有提到React可以透過props帶到子元件中,或是將要放入這個元件中的內容,透過children的方式帶到子元件中。今天再來透過這些方式看看怎麼達到類似Vue slot的效果,讓自己可以客製共用的元件。

類似一般的slot - 使用children把想要顯示的element放入

想要達到一般slot的效果其實只要使用children這個props就可以了,所以可以這樣客製自己的元件。

const CustomSlotComponent = ({ children }) => {
  return (
    <div className="container">
      <p>{ children }</p>
    </div>
  )
}
export default CustomSlotComponent;

當要使用時,可以這樣使用。因為如果單純只是把內容放在子元件中,就會被認定為那個內容是名為children的props。

import CustomSlotComponent from './components/CustomSlotComponent';

function App() {
  return (
    <div className="App">
      // slot content這個字串會被當作children往下帶進子元件中
      <CustomSlotComponent>slot content</CustomSlotComponent>
    </div>
  );
}
export default App;

https://ithelp.ithome.com.tw/upload/images/20230918/20130914UkrtXXRS9Y.png

除了塞入單純的文字外,也可以放入完整的一段HTML內容。

import CustomSlotComponent from "./CustomSlotComponent";

export default function App() {
  return (
    <div className="App">
      <CustomSlotComponent>
        <h1>標題</h1>
        <p>一些說明</p>
      </CustomSlotComponent>
    </div>
  );
}

https://ithelp.ithome.com.tw/upload/images/20230918/20130914CeVlqwnMtp.png

類似named slot - 使用特定名稱的props

如果一個客製化的元件中,會被挖好幾個洞,依照實際使用的情境放入特定的內容時,一樣也可以使用props來放入。在這個情境中,我設計了一個可以把title和description放入特定位置的元件。

const CustomNamedSlotComponent = (props) => {
  const { title, description } = props;
  return (
    <div className="container">
      <h1>{title}</h1>
      <p>{description}</p>
    </div>
  );
};

export default CustomNamedSlotComponent;

在實際使用時,就可以透過title、description把想要放入的內容帶入。

import CustomNamedSlotComponent from "./CustomNamedComponent";

export default function App() {
  return (
    <div className="App">
      <CustomNamedSlotComponent
        title="item 1 title"
        description="item 1 description"
      />
      <CustomNamedSlotComponent
        title="item 2 title"
        description="item 2 description"
      />
    </div>
  );
}

https://ithelp.ithome.com.tw/upload/images/20230918/20130914iItYkiABD7.png

利用render props呈現scoped slot的效果

render props是我們昨天有提到過的一種props的用法,主要是透過props把函式帶到子元件中,再透過這個函式來使用被保存在子元件內的邏輯或狀態,這個做法可以用來把共用的邏輯或資料抽放在客製的元件中,當使用這個元件時,就可以把這些邏輯拿出來用,而不需要把固定的這些邏輯放到各個使用到這個子元件的父元件中。

const DiscountItem = ({ render }) => {
  const discount = 0.8;
  return <p>計算後價格:{render(discount)}</p>;
};

export default DiscountItem;
import DiscountItem from "./DiscountItem";

export default function App() {
  const price = 100;
  return (
    <div className="App">
      <DiscountItem render={(discount) => discount * price} />
    </div>
  );
}

用這樣的方式,就可以達到類似Vue Scoped Slot的效果,也就能把被放在子元件的state拿來顯示畫面。

總結

雖然Vue有v-slot的語法可以來製做共用性高的元件,但是React一樣可以透過prope達到相同的效果,而且一樣也是用寫JavaScript的方式下去做這部分的設計。可能從Vue轉換到React後,會有點不太適應這個用法,但是只要記得React的functional component就是一個JavaScript的函式,就比較能快一點掌握相關的用法,我自己甚至覺得React的用法比起Vue更好懂、更好上手。結束了今天這個比較輕鬆簡單的主題,明天接著來看看watch和useEffect的部分。

參考資料

render props
React Reference Guide: Render props


上一篇
【Day 13】Vue與React實現互動效果的事件綁定
下一篇
【Day 15】究竟是watch?還是生命週期API?處理副作用的useEffect
系列文
從Vue學React!不只要會用,還要真的懂~30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言