對vue比較熟悉的朋友們,應該對於slot的使用都不陌生,slot對於增加元件的使用彈性上有著很大的幫助。但是React沒有vue框架中的v-slot,要怎麼彈性的設計一個可以複用的元件呢?今天一樣從Vue開始看,再來看看React的處理方式。
v-slot的用法我記得我自己在初學vue的時候,也沒有很熟悉,直到真正了解它之後,才知道它的好用之處。其實可以把v-slot的用途當作是在遊戲中,把武器鑲嵌寶石的概念,同一把大刀當鑲嵌了不同寶石後,長相或功能就有可能會不同,但實際上還是基底還是一把大刀。所以v-slot就是一個可以幫助使用者在Vue Template中設計一個可共用的元件的語法
,而它的存在就像是把HTML的結構挖出一個個的插槽一樣,會把依照資料有不同顯示的地方挖插槽,等到要使用時,再把對應的資料放入。
在Vue的v-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的內容而有不同。
與只有一個插槽的子元件一樣,也需要透過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>
在子元件透過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>
這個用法通常會把使用這個元件時,一定會用到的state包裝在這個元件中,等到實際使用時,再依照使用情境去活用這個元件內透過slot綁定的state。例如:使用這個元件一定會帶有8折的計算,就可以透過這樣的方式,把8折帶出來,計算後顯示在畫面上。
雖然說React並沒有像Vue一樣有slot這樣的用法,但是React還是可以達到相同的效果,靠得就是React的props
。昨天有提到React可以透過props帶到子元件中,或是將要放入這個元件中的內容,透過children的方式帶到子元件中。今天再來透過這些方式看看怎麼達到類似Vue slot的效果,讓自己可以客製共用的元件。
想要達到一般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;
除了塞入單純的文字外,也可以放入完整的一段HTML內容。
import CustomSlotComponent from "./CustomSlotComponent";
export default function App() {
return (
<div className="App">
<CustomSlotComponent>
<h1>標題</h1>
<p>一些說明</p>
</CustomSlotComponent>
</div>
);
}
如果一個客製化的元件中,會被挖好幾個洞,依照實際使用的情境放入特定的內容時,一樣也可以使用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>
);
}
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