Рекурсивный компонент — это компонент, который внутри собственного шаблона использует сам себя. Такой подход применяется для отображения и обработки иерархических, вложенных, древовидных структур данных, глубина которых заранее неизвестна или может динамически меняться.
Типичные примеры задач:
Vue.js поддерживает рекурсию на уровне компонентов, но требует строгого соблюдения правил, чтобы избежать бесконечного рендеринга.
Рекурсивный компонент строится на двух обязательных условиях:
name)Компонент вызывает сам себя только при выполнении определённого условия. Если условие отсутствует, Vue попытается рендерить компонент бесконечно, что приведёт к переполнению стека.
Простейший пример компонента, который отображает вложенный список элементов:
<template>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.title }}
<RecursiveList
v-if="item.children && item.children.length"
:items="item.children"
/>
</li>
</ul>
</template>
<script>
export default {
name: 'RecursiveList',
props: {
items: {
type: Array,
required: true
}
}
}
</script>
Ключевые моменты:
name: 'RecursiveList' позволяет компоненту ссылаться на
себяchildrenВо Vue рекурсивный вызов возможен только по имени компонента, а не по переменной импорта.
Некорректно:
components: { RecursiveList }
и затем использование <RecursiveList /> без
name.
Корректно:
export default {
name: 'RecursiveList'
}
Vue использует name как идентификатор при компиляции
шаблона. Без него рекурсия не будет работать.
Условие остановки — критически важная часть рекурсивного компонента. Оно определяет момент, когда вложенный вызов прекращается.
На практике используются следующие варианты:
isLeaf, expanded);Пример с ограничением глубины:
<RecursiveNode
v-if="level < maxLevel"
:node="child"
:level="level + 1"
/>
Каждый уровень рекурсии — это отдельный экземпляр компонента со своим состоянием и пропсами.
Обычно передаются:
Пример структуры данных:
const tree = [
{
id: 1,
title: 'Корень',
children: [
{
id: 2,
title: 'Дочерний узел',
children: []
}
]
}
]
Компонент принимает один узел и отображает его рекурсивно:
<template>
<div class="node">
{{ node.title }}
<div class="children" v-if="node.children?.length">
<TreeNode
v-for="child in node.children"
:key="child.id"
:node="child"
/>
</div>
</div>
</template>
<script>
export default {
name: 'TreeNode',
props: {
node: Object
}
}
</script>
В Vue 3 рекурсия одинаково работает как с Options API, так и с Composition API.
Пример с <script setup>:
<script setup>
defineProps({
node: Object
})
</script>
<template>
<div>
{{ node.title }}
<TreeNode
v-for="child in node.children || []"
:key="child.id"
:node="child"
/>
</div>
</template>
Имя компонента в этом случае определяется именем файла
(TreeNode.vue). Это автоматически делает рекурсию возможной
без явного name.
Частая задача — управление состоянием каждого уровня (раскрыт/свернут, выбран, активен).
Состояние может храниться:
Пример локального состояния:
<script>
export default {
name: 'TreeNode',
props: {
node: Object
},
data() {
return {
open: false
}
}
}
</script>
<div @click="open = !open">
{{ node.title }}
</div>
<TreeNode
v-if="open"
v-for="child in node.children"
:key="child.id"
:node="child"
/>
Каждый уровень управляет собственным состоянием независимо.
Рекурсивные компоненты часто должны уведомлять родительские уровни о событиях:
Используется проброс событий вверх:
<TreeNode
@select="emit('select', $event)"
/>
emits: ['select']
Таким образом событие поднимается через всю цепочку до корневого компонента.
Рекурсивный рендер требует внимания к производительности:
Рекомендации:
key обязательно;v-show вместо v-if, если
структура статична;Отсутствие условия остановки Приводит к бесконечному
рендеру и ошибке Maximum call stack size exceeded.
Отсутствие имени компонента Рекурсия не работает без
name.
Мутация пропсов Изменение данных узла напрямую нарушает однонаправленный поток данных.
Глобальная регистрация без необходимости Рекурсивные компоненты чаще эффективнее как локальные.
Рекурсивные компоненты:
Один грамотно спроектированный рекурсивный компонент способен заменить десятки однотипных шаблонов, сохраняя при этом читаемость и предсказуемость поведения.