遞迴元件是指同一個元件裏不斷引用自己,造成重複一層元件包著一層元件的情況,直至該元件所渲染的資料沒有滿足你設定的 v-if
資料,就代表此遞迴結束,不會再往下層產生同一個元件。此技巧常用於製作樹狀結構的 menu。
雖然能應用此技巧的情況不多,但也曾經出現在常見面試題中,因此也值得花時間學習一下。以下會再作詳細解說。
以上是多層樹狀 menu 的示範。
本篇文章會以歌單為例子。
資料結構如下:
menu: [
{
title: "K-POP",
children: [
{
title: 'BTS',
children: [
{
title: "BE",
children: [
{
title: "Life Goes On",
},
{
title: "Dynamite",
},
{
title: "Blue & Gray",
},
],
},
],
}
]
},
{
title: "US-POP",
children: [
{
title: 'Billie Eilish',
children: [ {...} ]
}
]
},
]
需要留意的重點:
title
和 children
屬性。title
和 children
屬性。children
屬性。因此,可以總結出:
children
屬性。
children
--> 沒開始遞迴。children
--> 停止遞迴。就著以上特點,先看看如何規劃整段程式碼:
黑色就是 <Menu>
元件,即是我們要遞迴的元件。以下會逐步解說做法。
首先,建立 App.vue。在 App.vue,使用 v-for
把最外層的 title
,即是音樂類別印出來。
App.vue
<div class="menu-bg">
<Menu v-for="item in menu" :key="item.title" :item="item" />
</div>
所有資料
data() {
return {
menu: [
{
title: "K-POP",
children: [{...}]
},
{
title: "US-POP",
children: [{...}]
},
]
}
}
加上一個灰色背景:
.menu-bg {
background: #ededed;
}
Menu.vue:
<template>
<p>
{{ item.title }}
</p>
</template>
export default {
props: {
item: {
type: Object
}
}
};
第一步是把最外層印出來,並在 Menu 元件使用 item
來接收 menu
裏的 2 個物件。像是這樣:
<div>
<!-- Menu 元件 -->
<p> K-POP </p>
<!-- Menu 元件 -->
<p> US-POP </p>
</div>
結果:
目前還不是樹狀 menu,只有把最外層印出來。舉例說,我們要把 title: K-POP
的 children 裏的資料渲染出來。做法很簡單:
item.children
取出 children 資料。title
、children
。<Menu />
元件,即是在再次引用自己。Menu.vue
<template>
<div>
<p>
{{ item.title }}
</p>
<Menu v-for="item in item.children" :key="item.title" :item="item" />
</div>
</template>
注意,在元件裏引用自己時,需要加入 name
屬性:
export default {
// 加入 name,template 才認得
name: 'Menu',
props: {
item: {
type: Object
}
}
};
結果是把所有資料都渲染出來:
打開 Vue 檢查工具就一目了然:
但是,現在看來不像樹狀 menu,因為:
children
屬性,會造成渲染錯誤。v-if
終止遞迴,以及加入所需的 CSS先加入 CSS,讓整體看來像一個樹狀 menu:
<template>
<div>
<p>
{{ item.title }}
</p>
<Menu
v-for="item in item.children"
:key="item.title"
:item="item"
class="sub-menu"
/>
</div>
</template>
<style scoped>
.sub-menu {
margin-left: 30px;
}
</style>
之後,加入 toggle 開關切換顯示子 menu 的內容:
<template>
<div>
<div class="title-bar">
<p>
{{ item.title }}
</p>
<a class="toggle-icon" href="#" @click.prevent="isOpen = !isOpen">
{{ isOpen ? "-" : "+" }}
</a>
</div>
<Menu
v-show="isOpen"
v-for="item in item.children"
:key="item.title"
:item="item"
class="sub-menu"
/>
</div>
</template>
data() {
return {
isOpen: false,
};
}
.title-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 50px;
}
.toggle-icon {
color: grey;
text-decoration: none;
}
結果:
最後,加入 v-if
來設定終止遞迴的條件:沒有 children 屬性。
<template>
<div>
<div class="title-bar">
<p>
{{ item.title }}
</p>
<a class="toggle-icon" href="#" @click.prevent="isOpen = !isOpen">
{{ isOpen ? "-" : "+" }}
</a>
</div>
<template v-if="item.children">
<Menu
v-show="isOpen"
v-for="item in item.children"
:key="item.title"
:item="item"
class="sub-menu"
/>
</template>
</div>
</template>
https://codesandbox.io/s/shu-zhuang-menu-db6wu?file=/src/components/Menu.vue
VueJS - Recursive Components
重新認識 Vue.js - 2-2 元件之間的溝通傳遞