https://vuejs.org/examples/#tree
컴포넌트의 재귀적 사용을 보여주는 간단한 트리 뷰 구현의 예이다.

부모 컴포넌트인 TreeExample.vue 파일과 자식 컴포넌트인 TreeView.vue 로 나누어 재귀적으로 표현해보고자한다.
먼저 부모 컴포넌트에서 부모와 자식을 관계를 나타내는 객체를 생성한다.
name 과 children 으로 이루어져 있으며, 자식이 있다면 children 배열이 존재하고, 그렇지 않다면 존재하지 않는 그런 구조이다.
const treeData = ref({
name: "My Tree",
children: [
{ name: "hello" },
{ name: "world" },
{
name: "child folder",
children: [
{
name: "child folder",
children: [{ name: "hello" }, { name: "world" }],
},
{ name: "hello" },
{ name: "world" },
{
name: "child folder",
children: [{ name: "hello" }, { name: "world" }],
},
],
},
],
});
이 객체는 자식 컴포넌트에서도 쓰일 예정이기 때문에 자식 컴포넌트의 props 로 넘겨줄 예정이다.
따라서 html 은 다음과 같이 작성해준다.
<template>
<ul>
<TreeView
:model="treeData"
></TreeView>
</ul>
</template>
부모 컴포넌트에서 생성한 객체를 자식 컴포넌트에서 props 로 정의하여 받아오도록 한다.
const props = defineProps({
model: Object,
});
이제 자식 컴포넌트에서 동작하는 로직을 생성해야한다. 로직의 과정은 다음과 같다.
[+]문자열을 클릭한다. -> 문자열은 [-]로 변경된다.+문자열을 클릭하여 new stuff 를 추가할 수 있다.그렇다면, 클릭해야하는 부분은 눈으로 보이는 것은 두가지로 정리할 수 있다.
1-1. children 이 있는 경우 -> [+]문자열을 클릭한다. -> 문자열은 [-]로 변경된다. -> isOpen
const isOpen = ref(false);
function toggle(){
isOpen.value = !isOpen.value;
}1-2. children 을 나타낸다. -> isFolder
const isFolder = computed(() => {
//자식이 존재한다면 isFolder 를 + 로
return props.model.children && props.model.children.length;
});+문자열을 클릭하여 new stuff 를 추가하는 경우 <li @click="addChild">+</li>
function addChild() {
props.model.children.push({ name: "new stuff" });
}
li 태그를 클릭시 addChild 메서드가 실행이 되는데, props로 정의한 model 의 children에 name 이 new stuff 인 값을 추가하도록 push 해주었다.

하지만 위와 같이 자식 컴포넌트에서 props.model.children 을 직접 변경하려고 하고 있기 때문에 오류가 발생하였다. vue 에서는 부모에서 자식으로 전달된 props 객체는 변경이 금지되어 있다. 대신, 변경사항을 부모에게 알리고 부모 컴포넌트에서 데이트를 업데이트하는 방법을 사용한다면 변경할 수 있다.
따라서, 다시 한번 부모 컴포넌트에서의 코드를 살펴보면
//TreeExample.vue
function addChildToTreeData(model) {
model.children.push({ name: "new stuff" });
}
<TreeView
:model="treeData"
:addChild="addChildToTreeData"
></TreeView>
로직은 똑같은 addChildToTreeData 함수와 props로 전달해준다.
그렇다면 자식 컴포넌트에서 props 를 추가한다.
//TreeView.vue
const props = defineProps({
model: Object,
addChild: Function, //추가한 함수
});
function addChild() {
props.addChild(props.model);
}
기존의 addChild 메서드에서는 바로 추가하는 로직이었지만, props.addChild 에 매개변수로 함께 접근하는 것으로 변경해주었다.
| 그림 | 코드 |
|---|---|
![]() | ![]() |
그림과 코드를 같이 보면 반복되는 구조를 가지는 것을 볼 수 있다.
리스트를 표현하는 template 이 있는 자식 컴포넌트인 TreeView.vue 에 TreeView.vue 를 중첩시켜주면 된다.
부모 컴포넌트인 TreeExample.vue 에서 TreeView.vue 에 필요한 props 를 입력해주면 되는 것처럼 필요한 데이터와 추가적으로 필요한 데이터를 입력해주면된다.
<TreeView
v-for="childModel in model.children"
:model="childModel"
:key="childModel.name"
:addChild="props.addChild"
></TreeView>
필수적인 필요한 props는 model과 addChild 메서드였다.
먼저 model 은 현재 model 의 children 으로써 배열의 형태이기 때문에 v-for를 이용한다.
key는 유일성을 나타내는 것이기에 childModel.name 을 사용한다.
addChild 는 부모 컴포넌트에서 선언한 메서드를 그대로 이용하면 되기 때문에 props.addChild 를 이용한다.
<script setup>
import { ref } from "vue";
import TreeView from "./TreeView.vue";
const treeData = ref({
name: "My Tree",
children: [
{ name: "hello" },
{ name: "world" },
{
name: "child folder",
children: [
{
name: "child folder",
children: [{ name: "hello" }, { name: "world" }],
},
{ name: "hello" },
{ name: "world" },
{
name: "child folder",
children: [{ name: "hello" }, { name: "world" }],
},
],
},
],
});
function addChildToTreeData(model) {
model.children.push({ name: "new stuff" });
}
</script>
<template>
<ul>
<TreeView
class="item"
:model="treeData"
:addChild="addChildToTreeData"
></TreeView>
</ul>
</template>
<style>
.item {
cursor: pointer;
line-height: 1.5;
}
.bold {
font-weight: bold;
}
</style>
<script setup>
import { ref, computed, defineProps } from "vue";
const props = defineProps({
model: Object,
addChild: Function,
});
const isOpen = ref(false);
const isFolder = computed(() => {
//자식이 존재한다면 isFolder 를 + 로
return props.model.children && props.model.children.length;
});
function toggle() {
isOpen.value = !isOpen.value;
}
function addChild() {
props.addChild(props.model);
// props.model.children.push({ name: "new stuff" });
}
console.log("isFolder : ", isFolder);
</script>
<template>
<li>
<div :class="{ bold: isFolder }" @click="toggle">
{{ model.name }}
<span v-if="isFolder"> [{{ isOpen ? "-" : "+" }}] </span>
</div>
<ul v-if="isFolder" v-show="isOpen">
<TreeView
v-for="childModel in model.children"
:model="childModel"
:key="childModel.name"
:addChild="props.addChild"
></TreeView>
<li @click="addChild">+</li>
</ul>
</li>
</template>
<style></style>
기존 코딩을 할 때 재귀는 까다로운 알고리즘이라고 생각한 적이 있는데, vue 를 이용하는 법도 꽤나 어려웠다. 필요한 데이터를 부수적으로 추가할 수 있어야 하고, vue 특성상 부모의 메서드를 함부로 건드릴 수 없다는 것을 잘 알게 되었다.
다음은 간단한 todo 웹 예제를 살펴볼 예정이다.