Implementing Recursive Components in Vue 3
Introduction
In daily Vue projects, component libraries are often used to assist development, so the exposure to recursive components might not be very high. However, this doesn't mean recursive components are unimportant.
This article will help you master the usage of recursive components in about 10 minutes.
Before proceeding, you must have a basic understanding of: html + css + js + Vue3 fundamentals, at least knowing what a Vue component is.
Usage Explanation
Before diving into recursive components, let's clarify a few concepts.
What is Recursion?
Recursion, as defined in Baidu Encyclopedia, is:
The programming technique where a program calls itself is called recursion.
You can roughly think of recursion as a loop, but recursion involves calling itself.
In practice, you need to set a boundary condition for recursion to determine whether to continue recursing.
Without setting a condition, it will lead to infinite recursion, i.e., an endless loop!
What is a Recursive Component?
By now, I believe you know what a Vue component is.
I'll place the Vue3 Recursive Component Documentation here.
Essentially, a recursive component combines "recursion" and "component."
The component calls itself within the boundary condition untill the condition is no longer met.
Where are Recursive Components Used?
In my work, recursive components are used in scenarios such as:
- Tree components: Used to display file hierarchies.
- Left navigation bar: Navigation menus generated based on route hierarchies.
- Multi-level tables (nested tables).
Hands-on Practice
After the previous explanation, you should have a basic concept of recursive components.
Next, we'll explain through a simple example.
The image above is the example we'll implement.
I haven't written any styles, so please use your imagination to visualize this as a website's left navigation bar.
Such navigation bars can be hardcoded on the frontend; or in business scenarios, they might be configured by the backend, with the frontend requesting the navigation data and then generating routes.
Thus, it can be understood that the frontend initially doesn't know how many leveels this navigation has.
This is where recursive components can be utilized.
Steps:
- Create a navigation component
- Register the navigation component globally
- Fetch data (for learning purposes, we'll hardcode the data in the frontend)
- Set recursive boundaries in the navigation component and render the data
1. Create the Component
I'll name the navigation component NavigationTree.vue and place it in the components directory.
NavigationTree.vue
<template>
<div>
NavigationTree
</div>
</template>
<script setup>
</script>
<style lang="scss" scoped>
</style>
At this point, the project directory is as follows:
Omitted some directories and files
- src
|- main.js
|- App.vue
|- components
|- NavigationTree.vue
2. Register the Component Globally
I'll register the NavigationTree.vue component globally, making it convenient for NavigationTree.vue to call itself.
main.js
import { createApp } from 'vue'
import App from './App.vue'
import NavigationTree from './components/NavigationTree.vue' // Import NavigationTree component
const app = createApp(App)
app.component('NavigationTree', NavigationTree) // Register NavigationTree as a global component
app.mount('#app')
Using it in App.vue
App.vue
<template>
<div>
<NavigationTree />
</div>
</template>
The browser interface now looks like the image above.
3. Fetch Navigation Data
In real projects, the left navigation might be fetched from the backend.
But the purpose of this article is to learn recursive components, so we'll directly simulate "requested data" in the frontend.
I'll place the "data fetching" operation in App.vue and then pass it to the NavigationTree.vue component via props.
Mentioning props, I'll briefly note: 10 Ways of Component Communication in Vue3
App.vue
<template>
<div>
<NavigationTree :items="navigationData" />
</div>
</template>
<script setup>
const navigationData = [
{
label: 'Level 1 Navigation 1'
},
{
label: 'Level 1 Navigation 2',
subItems: [
{ label: 'Level 2 Navigation 2-1' },
{
label: 'Level 2 Navigation 2-2',
subItems: [
{ label: 'Level 3 Navigation 2-2-1' },
{ label: 'Level 3 Navigation 2-2-2' },
]
},
{ label: 'Level 2 Navigation 2-3' }
]
},
{
label: 'Level 1 Navigation 3'
}
]
</script>
NavigationTree.vue
<template>
<div>
NavigationTree
</div>
</template>
<script setup>
const props = defineProps({
items: {
type: Array,
default: () => []
}
})
</script>
Now, NavigationTree.vue has received the "requested navigation data."
4. Set Recursive Boundaries and Render Data
We see that the navigation data has a subItems field, which means "submenu."
We can determine whether to continue recursing based on the presence of the subItems field. In other words, subItems is the boundary condition for recursion.
NavigationTree.vue
<template>
<ul>
<template v-for="node in items" :key="node.label">
<li>{{ node.label }}</li>
<NavigationTree v-if="'subItems' in node" :items="node.subItems" />
</template>
</ul>
</template>
<script setup>
const props = defineProps({
items: {
type: Array,
default: () => []
}
})
</script>
The key part is in the HTML code.
This completes the initial goal.
Complete Code
main.js
import { createApp } from 'vue'
import App from './App.vue'
import NavigationTree from './components/NavigationTree.vue'
const app = createApp(App)
app.component('NavigationTree', NavigationTree)
app.mount('#app')
App.vue
<template>
<div>
<NavigationTree :items="navigationData" />
</div>
</template>
<script setup>
const navigationData = [
{
label: 'Level 1 Navigation 1'
},
{
label: 'Level 1 Navigation 2',
subItems: [
{ label: 'Level 2 Navigation 2-1' },
{
label: 'Level 2 Navigation 2-2',
subItems: [
{ label: 'Level 3 Navigation 2-2-1' },
{ label: 'Level 3 Navigation 2-2-2' },
]
},
{ label: 'Level 2 Navigation 2-3' }
]
},
{
label: 'Level 1 Navigation 3'
}
]
</script>
components/NavigationTree.vue
<template>
<ul>
<template v-for="node in items" :key="node.label">
<li>{{ node.label }}</li>
<NavigationTree v-if="'subItems' in node" :items="node.subItems" />
</template>
</ul>
</template>
<script setup>
const props = defineProps({
items: {
type: Array,
default: () => []
}
})
</script>