前兩篇對我們大概知道元件可以接受 props
,而 props
通常是 JavaScript 資料型別(物件或一般型別)
。
但在某些情況下,我們可能會希望將一段模板片段(template)
傳遞給子元件,並讓子元件在其自己的模板中渲染這個片段。
我們可以使用插槽(slots)
來實現這樣的需求,讓父元件能夠動態地將模板內容傳遞給子元件並進行渲染,這樣的設計能夠保持子元件的靈活性,同時讓父元件在不同情境下決定子元件的顯示邏輯,一種鬆耦合的特徵
。
slot
靜態和具名插槽認識、元件render scope觀念slot props
將子元件插槽資料與父元件做溝通slot props
實際應用案例-(slot props use case)在 Vue 中如果真的需要擴展子元件顯示的方式,來實現不同的商業邏輯,像是丟客制化樣板進去當作子元件參數,並且將一些更新事件主導權給父元件,基本上可以使用插槽(slots)
來實現。
基本 slot(或默認 slot)
是一個在子元件裡面的標籤佔位符號,允許父元件向子元件插入樣板內容,只需要定義一個 slot
,父元件便可以將內容插入到這個slot
中。
元件內的插槽可以有多個,並且可以具名(named),這樣你就可以元件中定義多個插槽區域,讓父元件在開發上可以根據需要傳遞不同的模板片段到指定的具名插槽,去做顯示位置的定義。
像是下方範例 MyComponent.vue
定義了三個插槽:header
、default(預設插槽)
和 footer
。
父元件可以使用 v-slot
指令將模板片段傳遞到這些具名插槽,像是 v-slot:header
和 v-slot:footer
,以及直接放置在預設插槽的位置。
在slot
定義好的樣板或是接收到的JavaScript表達式,只有父元件才能跟它溝通和控制資料,子元件僅能負責接收樣板並負責顯示
,並不會接觸動用到父層透過slot傳入的樣板資料。
其實可以視為每一個元件經過Vue編譯後,都有自己的資料和樣板結構作用域(scope)
。
Expressions in the parent template only have access to the parent scope; expressions in the child template only have access to the child scope.
當插入的內容是靜態樣板時的或完全由父元件控制渲染資料時,基本 slot
是滿適合的選擇。
可用於簡單的組件,例如一個對話框組件
,父元件可以將對話框的標題和正文內容作為 slot
插入。
對於一些內容排版比較固定的 UI layout元件,我們可以用基本 slot來設定。
<!-- 子元件 MyComponent.vue -->
<template>
<div>
<header><slot name="header"></slot></header>
<main><slot></slot></main>
<footer><slot name="footer"></slot></footer>
</div>
</template>
<!-- 父元件 -->
<template>
<MyComponent>
<!--message 由父組件資料流控制-->
<template v-slot:header>這是標題 {{ message }}</template>
這是正文內容
<template v-slot:footer>這是頁腳</template>
</MyComponent>
</template>
<script setup>
import {ref} from 'vue'
const message = ref('Hello world')
</script>
像是實務上拿到樣式稿分析時常出現的Modal彈窗元件
,就會習慣用slot
來分配欄位名稱來開發製成共用元件,像下面版型其實會是由一個共用Modal包裝出來的。
這是Vue官方有提及的一項插槽間資料的溝通,不過一開始接觸會覺得很玄的觀念,希望能在這次文章中探究清楚囉~~~
來看一下定義:
Scoped Slots
觀念是基本上 slot
的一種擴展,允許子元件將自己的資料或方法暴露給父元件,因為上面有提到元件間的render scope觀念彼此的資料流是不能互相觸及的。
使得父元件可以基於子元件的數據來決定渲染內容,父元件可以利用slot Props訪問子元件在 slot 中提供的變數,並且重新在父元中設計插槽內提供的樣式。
不過比較常用的情境是,通常子元件是比較通用靜態基礎的顯示UI
,顯示資料透過父元件資料層以props
傳入:
props資料
後,綁定資料到slot
插槽上v-slot
,將子組件slot綁定的資料提取出來到父組件樣板中使用
。看到這段用法一定會有很多黑人問號????,那為什麼不直接user資料直接丟到父元件slot template中渲染,有必要到子元件過個水再拋回來???
// 子元件
<!-- <MyComponent> template -->
<div>
<slot :text="props.greetingMessage" :count="1"></slot>
</div>
// 父元件使用
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
來看到蒐集到的文章範例,應該會對slot props
特殊用法的情境會有更深刻理解:
假設你要製作一個Table表單元件,會提供每行數據的細節,希望父元件能夠根據每行數據的狀態(可能是圖案,或是隨商業邏輯變換)進行不同的UI渲染
,子組件在拆分可能會以v-for列表
渲染設計並置入slot
插槽:
可以看到子元件的架構很簡單,就是單純渲染傳入的資料,通常這種UI因為很單純沒有太多奇怪商業邏輯,共用性會很高。
<!-- DataTable.vue -->
<template>
<table>
<tr v-for="row in rows" :key="row.id">
<slot :row="row"></slot>
</tr>
</table>
</template>
<script setup>
const props = defineProps({
rows: {
type: Array,
}
}
)
</script>
你可以根據每行數據的內容來決定如何渲染,是像數據超過某過值顯示不同UI內容
,因為每行數據渲染內容很多變,避免未來業務需求增加,寫一堆判斷式疊加破壞子元件已經設計好的邏輯的話很適用。
<!-- Parent.vue -->
<template>
<DataTable :rows="financialData" v-slot="{ row }">
<td>{{ row.name }}</td>
<td :class="{'positive': row.value > 0, 'negative': row.value < 0}">
{{ row.value }}
<!--這裡如果定義在子組件內的slot裡面 會使得子組件共用性降低,
因為可能未來UI渲染顯示又不一樣-->
<span v-if="row.value > threshold">⚠️</span>
<span v-else>🤡</span>
<!--可以根據每一個父組件邏輯去增加設定,子組件脫離耦合性-->
</td>
<td>{{ row.date }}</td>
</DataTable>
</template>
<script setup>
import {ref} from 'vue
const financialData = ref([
{ id: 1, name: 'Revenue', value: 150000, date: '2023-01-01' },
{ id: 2, name: 'Expenses', value: -50000, date: '2023-01-01' },
{ id: 3, name: 'Net Profit', value: 100000, date: '2023-01-01' }
],
};)
const threshold = ref(100000)
</script>
當你構建一個通用的表單組件時,可能需要根據不同的輸入類型來渲染對應輸入元素,slot顯示內容順序也不是那麼固定的話。
使用 slot Props
,父元件可以接收子元件傳遞的輸入數據和狀態,並且能夠靈活更動表單的UI組合順序,可以一次滿未來不同業務UI上調動的需求~!。
<template>
<div v-for="field in fields" :key="field.name" class="input-row">
<div class="title">{{ field.label }}</div>
<slot :name="field.type" :field="field"></slot>
</div>
</template>
<script setup>
const props = defineProps({
fields: {
type: Array,
}
}
)
</script>
<style scoped>
.input-row {
display: flex;
}
.title {
width: 200px;
}
</style>
<!-- ParentComponent.vue -->
<template>
<FormComponent :fields="data.fields">
<template v-slot:text="{ field }">
<input type="text" :placeholder="field.label" />
</template>
<template v-slot:checkbox="{ field }">
<input type="checkbox" :label="field.label" />
</template>
</FormComponent>
</template>
<script setup>
import {ref} from 'vue'
import FormComponent from './FormComponent.vue'
const data = ref({
fields: [
{ name: 'username', label: 'user name', type: 'text' },
{ name: 'subscribe', label: 'Subscribe to newsletter', type: 'checkbox' },
{ name: 'age', label: 'user age', type: 'text' }
]
})
</script>
slot props
的應用場景非常適合那些需要根據父元件的傳遞過來數據變化而動態渲染不同內容
的情況,由父層的資料來控制具體插槽內容的顯示邏輯或順序。
父元件負責提供數據或狀態(Data provider component)、渲染邏輯(條件切換等)處理
,子元件則專注負責畫面顯示UI-View上的設計
,這樣可以保持子元件的簡單性和通用性,降低彼此的耦合度。
這樣看起來父元件間即使有較複雜或變動性比較高的商業邏輯,就能使用,因為不用再更動已經設計好的基礎UI子元件,這裡也有一種元件有商業資料邏輯處理層(Data)
和一般畫面(View)
切割開的概念~!
如果渲染邏輯是固定的或僅在少數情況下會變化,並且子元件不需要為不同的父元件提供不同的渲染邏輯,那麼直接在內部渲染slot內容是一個簡單而有效的解決方案 → slot 靜態和具名插槽
。
但是如果你的應用場景需要父元件根據不同的需求,特別是數據邏輯不同有不同呈現,去進行動態渲染,尤其是v-for列表渲染
--> 使用 slot props 會提供更大的靈活性和可重用性
。但一般情況下這麼用資料流會比較多拋接過程,程式碼可讀性比較低,UI很靜態的話就不要過度使用囉~