Vue 作用域插槽 (Scoped Slots) 詳解:打破作用域限制的元件橋樑
在 Vue 的元件體系中,預設遵循著嚴格的單向資料流與作用域限制:父元件無法直接存取子元件內部的資料。
然而,當您開發高度封裝的元件(例如:資料表格 DataTable、自動完成列表 Autocomplete)時,由子元件提供資料,但由父元件決定渲染樣式 的需求便會出現。這正是 作用域插槽 (Scoped Slots) 大顯身手的時刻。
為什麼需要作用域插槽?
一句話總結:子元件掌握 資料(Data),但不知道如何渲染;父元件掌握 渲染邏輯(UI),但拿不到資料。 作用域插槽就像一座橋樑,讓子元件能像 傳遞 Props 一樣,把內部數據回傳給父元件的插槽內容。
先看清楚為什麼一般插槽做不到。插槽內容在 父元件的作用域 編譯,所以下面這段一定拿不到值:
<template>
<!-- ❌ u 是 UserList 內部 v-for 的變數,父元件看不到 -->
<UserList>{{ u.name }}</UserList>
</template>實作步驟:從資料綁定到接收
子元件:定義插槽並綁定數據
在子元件中,我們透過 :propName="value" 的方式,將資料掛載到 <slot> 標籤上。
<!-- /components/UserList.vue -->
<script setup>
import { reactive } from "vue";
const users = reactive([
{ id: 9, name: "Away", role: "admin" },
{ id: 10, name: "Tony", role: "user" },
{ id: 11, name: "Mary", role: "user" },
]);
</script>
<template>
<ul>
<li v-for="u in users" :key="u.id">
<slot :user="u"></slot>
</li>
</ul>
</template>這裡的 :user="u" 語法與 綁定 props 幾乎一樣,只是對象從子元件換成了插槽內容。至於 v-for 與 :key 的用法,可以參考 列表渲染。
父元件:接收數據並自定義外觀
父元件透過 v-slot 指令接收一個物件(通常命名為 slotProps),該物件包含了子元件回傳的所有數據。
<!-- /views/Slot.vue -->
<script setup>
import UserList from "@/components/UserList.vue";
function clickHandler(id) {
console.log(id);
}
</script>
<template>
<UserList v-slot="slotProps">
{{ slotProps.user.name }} ({{ slotProps.user.role }})
<input
type="button"
value="edit"
@click="clickHandler(slotProps.user.id)"
>
</UserList>
</template>TIP
簡單來說,子元件裡有資料,但不知道怎麼渲染;父元件知道怎麼渲染,但沒有資料。作用域插槽就是那座橋樑,讓子元件把資料傳給父元件的插槽內容。
進階技巧:解構插槽 Prop (Destructuring)
為了讓程式碼更簡潔,您可以使用 ES6 解構語法直接取得資料,這是現代 Vue 開發中最推薦的寫法:
<UserList v-slot="{ user }">
{{ user.name }}
</UserList>解構時還能一併改名或給預設值,處理欄位可能不存在的情況:
<UserList v-slot="{ user: person, isActive = false }">
{{ person.name }}
</UserList>不只能傳資料,也能傳方法
<slot> 上綁的東西不限於資料,子元件的方法同樣可以交出去,讓父元件決定要掛在哪個按鈕上:
<!-- 子元件 -->
<template>
<slot :user="u" :remove="() => removeUser(u.id)"></slot>
</template><!-- 父元件:UI 與觸發時機都由自己決定 -->
<UserList v-slot="{ user, remove }">
{{ user.name }}
<button @click="remove">刪除</button>
</UserList>這種把 資料與行為都交給外層 的設計,是元件庫(例如:表格、下拉選單)常見的做法,比不斷增加 props 或 emit 更容易擴充。
作用域插槽也能有預設內容
<slot> 標籤之間依然可以寫預設內容,而且 預設內容也用得到綁在 slot 上的資料:
<template>
<ul>
<li v-for="u in users" :key="u.id">
<slot :user="u">{{ u.name }}</slot>
</li>
</ul>
</template>如此一來,父元件不傳插槽內容時,列表仍然顯示得出使用者名稱,這也是 基本插槽 提到的不破版原則。
常見問題
作用域插槽和 props 有什麼不同?
方向相反。props 是父元件把資料交給子元件使用,作用域插槽則是子元件把自己內部的資料交給父元件寫的插槽內容使用,讓渲染方式留在父元件手上。
slotProps 這個名字可以改嗎?
可以,它只是一個參數名稱,寫成 v-slot="row" 或 v-slot="data" 都行。實務上更推薦直接解構成 v-slot="{ user }",程式碼更短也更清楚資料有哪些欄位。
為什麼父元件直接寫子元件的變數會拿不到值?
因為插槽內容是在 父元件的作用域 中編譯的,只看得到父元件的資料。子元件內部的資料必須由子元件綁到 <slot> 標籤上,父元件才接得到。
作用域插槽可以傳函式出去嗎?
可以。除了資料,也常把子元件的方法綁到 <slot> 上,例如:展開、關閉或重新載入,讓父元件自己決定要放在哪個按鈕上觸發。
作用域插槽有預設內容嗎?
有,寫在 <slot> 標籤之間的內容就是預設值,而且預設內容也能使用綁在 <slot> 上的資料。父元件沒有提供插槽內容時就會顯示它。