使用 Vue slot 讓組件更加靈活

前言

Vue.js 提供了強大的插槽(slot)功能,讓父組件能靈活地將內容傳遞給子組件。本文將介紹四種插槽的基本用法。

插槽(Slots)

插槽是 Vue 中最基本的使用方式,允許父組件在子組件中插入特定內容,如果插槽的內容只有一個則不用填寫 slot 名稱。

父組件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<h1>這是父組件</h1>
<hr />
<SlotComponent>
<p>這段內容將被插入到子組件的 slot 中。</p>
</SlotComponent>
</div>
</template>

<script>
import SlotComponent from '@/components/SlotComponent.vue';

export default {
components: {
SlotComponent,
},
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div>
<h1>這是父組件</h1>
<hr />
<SlotComponent>
<p>這段內容將被插入到子組件的 slot 中。</p>
</SlotComponent>
</div>
</template>

<script>
import { defineComponent } from 'vue'
import SlotComponent from '@/components/SlotComponent.vue'

export default defineComponent({
components: {
SlotComponent,
},
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<h1>這是父組件</h1>
<hr />
<SlotComponent>
<p>這段內容將被插入到子組件的 slot 中。</p>
</SlotComponent>
</div>
</template>

<script setup>
import SlotComponent from '@/components/SlotComponent.vue'
</script>

子組件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<h2>這是子組件</h2>

<!-- 預設的 slot -->
<slot></slot>
</div>
</template>

<script>
export default {
name: 'SlotComponent',
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<h2>這是子組件</h2>

<!-- 預設的 slot -->
<slot></slot>
</div>
</template>

<script>
import { defineComponent } from 'vue'

export default defineComponent({
name: 'SlotComponent',
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<h2>這是子組件</h2>

<!-- 預設的 slot -->
<slot></slot>
</div>
</template>

<script setup>
defineOptions({
name: 'SlotComponent',
})
</script>

Vue slot

具名插槽(Named slots)

具名插槽允許子組件定義多個插槽並指定名稱,這樣可以增強父組件在使用時的靈活性。

在 Vue 中,v-slot 可以縮寫為 #,後面接插槽的名稱(即 name 屬性),例如:#header。

父組件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div>
<h1>這是父組件</h1>
<hr />
<SlotComponent>
<template v-slot:header>
<h3>我是 header</h3>
</template>

<p>我是內容區塊</p>

<template v-slot:footer>
<p>我是 footer</p>
</template>
</SlotComponent>
</div>
</template>

<script>
import SlotComponent from '@/components/SlotComponent.vue';

export default {
name: 'ParentComponent',
components: {
SlotComponent,
},
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
<div>
<h1>這是父組件</h1>
<hr />
<SlotComponent>
<template #header>
<h3>我是 header</h3>
</template>

<p>我是內容區塊</p>

<template #footer>
<p>我是 footer</p>
</template>
</SlotComponent>
</div>
</template>

<script>
import { defineComponent } from 'vue'
import SlotComponent from '@/components/SlotComponent.vue'

export default defineComponent({
name: 'ParentComponent',
components: {
SlotComponent,
},
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<h1>這是父組件</h1>
<hr />
<SlotComponent>
<template #header>
<h3>我是 header</h3>
</template>

<p>我是內容區塊</p>

<template #footer>
<p>我是 footer</p>
</template>
</SlotComponent>
</div>
</template>

<script setup>
import SlotComponent from '@/components/SlotComponent.vue'
</script>

子組件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<h2>這是子組件</h2>
<slot name="header"></slot> <!-- 具名 slot -->
<slot></slot> <!-- 預設 slot -->
<slot name="footer"></slot> <!-- 具名 slot -->
</div>
</template>

<script>
export default {
name: 'SlotComponent',
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<h2>這是子組件</h2>
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</div>
</template>

<script>
import { defineComponent } from 'vue'

export default defineComponent({
name: 'SlotComponent',
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<h2>這是子組件</h2>
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</div>
</template>

<script setup>
defineOptions({
name: 'SlotComponent',
})
</script>

Vue named slots

作用域插槽(Scoped Slots)

作用域插槽允許父組件從子組件獲取數據,並將其傳遞到插槽中,實現根據子組件狀態或屬性動態渲染內容。

父組件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div>
<SlotComponent v-slot:default="{ message }">
<p>{{ message }}</p>
</SlotComponent>
</div>
</template>

<script>
import SlotComponent from '@/components/SlotComponent.vue';

export default {
components: {
SlotComponent,
},
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
<SlotComponent v-slot:default="{ message }">
<p>{{ message }}</p>
</SlotComponent>
</div>
</template>

<script>
import { defineComponent } from 'vue'
import SlotComponent from '@/components/SlotComponent.vue'

export default defineComponent({
components: {
SlotComponent,
},
})
</script>
1
2
3
4
5
6
7
8
9
10
11
<template>
<div>
<SlotComponent v-slot:default="{ message }">
<p>{{ message }}</p>
</SlotComponent>
</div>
</template>

<script setup>
import SlotComponent from '@/components/SlotComponent.vue'
</script>

子組件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>
<slot :message="childMessage"></slot>
</div>
</template>

<script>
export default {
data() {
return {
childMessage: '來自子組件的消息',
};
},
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<slot :message="childMessage"></slot>
</div>
</template>

<script>
import { defineComponent, ref } from 'vue'

export default defineComponent({
setup() {
const childMessage = ref('來自子組件的消息')
return { childMessage }
},
})
</script>
1
2
3
4
5
6
7
8
9
10
11
<template>
<div>
<slot :message="childMessage"></slot>
</div>
</template>

<script setup>
import { ref } from 'vue'

const childMessage = ref('來自子組件的消息')
</script>

Vue scoped slots

動態插槽(Dynamic Slots)

動態插槽允許根據條件渲染不同的插槽,適用於需要在同一組件中根據狀態顯示不同內容的情況。

父組件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<template>
<div>
<h1>這是父組件</h1>
<hr />
<button @click="toggleSlot">切換插槽</button>
<SlotComponent :currentSlot="currentSlot">
<template v-slot:slot1>
<p>這是第一個插槽的內容。</p>
</template>
<template v-slot:slot2>
<p>這是第二個插槽的內容。</p>
</template>
</SlotComponent>
</div>
</template>

<script>
import SlotComponent from '@/components/SlotComponent.vue';

export default {
components: {
SlotComponent,
},
data() {
return {
currentSlot: 'slot1', // 初始插槽
};
},
methods: {
toggleSlot() {
this.currentSlot = this.currentSlot === 'slot1' ? 'slot2' : 'slot1';
},
},
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template>
<div>
<h1>這是父組件</h1>
<hr />
<button @click="toggleSlot">切換插槽</button>
<SlotComponent :currentSlot="currentSlot">
<template #slot1>
<p>這是第一個插槽的內容。</p>
</template>
<template #slot2>
<p>這是第二個插槽的內容。</p>
</template>
</SlotComponent>
</div>
</template>

<script>
import { defineComponent, ref } from 'vue'
import SlotComponent from '@/components/SlotComponent.vue'

export default defineComponent({
components: {
SlotComponent,
},
setup() {
const currentSlot = ref('slot1') // 初始插槽

const toggleSlot = () => {
currentSlot.value = currentSlot.value === 'slot1' ? 'slot2' : 'slot1'
}

return {
currentSlot,
toggleSlot,
}
},
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div>
<h1>這是父組件</h1>
<hr />
<button @click="toggleSlot">切換插槽</button>
<SlotComponent :currentSlot="currentSlot">
<template #slot1>
<p>這是第一個插槽的內容。</p>
</template>
<template #slot2>
<p>這是第二個插槽的內容。</p>
</template>
</SlotComponent>
</div>
</template>

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

const currentSlot = ref('slot1') // 初始插槽

const toggleSlot = () => {
currentSlot.value = currentSlot.value === 'slot1' ? 'slot2' : 'slot1'
}
</script>

子組件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<slot :name="currentSlot"></slot>
</div>
</template>

<script>
export default {
props: {
currentSlot: {
type: String,
required: true,
},
},
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
<slot :name="currentSlot"></slot>
</div>
</template>

<script>
import { defineComponent } from 'vue'

export default defineComponent({
props: {
currentSlot: {
type: String,
required: true,
},
},
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<slot :name="currentSlot"></slot>
</div>
</template>

<script setup>
defineProps({
currentSlot: {
type: String,
required: true,
},
})
</script>

Vue dynamic slots

總結

透過上述範例,我們可以看到 Vue 的插槽功能如何有效提升組件的靈活性。無論是選擇哪個插槽,都能讓開發者在組件之間輕鬆傳遞內容,實現更複雜的 UI 結構。