1. 하고 싶었던 것
매체 카테고리 트리 구조를 Vue 3 + Vuetify 3로 구현하고 있었는데
카테고리 리스트가 트리 구조라서
“모두 펼치기” / “모두 닫기” 기능이 필요했다.
Vuetify 3에 v-treeview가 있어서
v-model:open으로 열고 닫을 노드 ID 배열만 넘겨주면 된다길래
“오 이거 쉽게 되겠네” 하고 가볍게 시작했다.
2. 첫 번째 시도 ( v-treeview + open 배열)
<v-treeview
:items="mediaItemCategoryVo"
v-model:open="expandedItems"
item-key="id"
item-children="children"
/>
expandAll() {
// 부모 노드만 id
this.expandedItems = this.mediaItemCategoryVo.map(item => item.id);
}
근데 막상 써보니까
부모 노드만 열리고, 자식 노드는 안 열린다.
3. 그러면 하위까지 다 넣으면 되지 않을까?
collectAllNodeIds(items: any[]): string[] {
const ids: string[] = [];
items.forEach(item => {
ids.push(item.id);
if (item.children?.length) {
ids.push(...this.collectAllNodeIds(item.children));
}
});
return ids;
}
expandAll() {
this.expandedItems = this.collectAllNodeIds(this.mediaItemCategoryVo);
}
부모 + 자식 id 전부 넣어줬다.
그래도 안 열림.
4. 대체 왜 안 되는 건데?
내부 코드를 살펴보니
if (this.open.includes(this.item.id)) {
// 해당 노드만 열림
// 자식은 무시
}
아… 이거
open 배열에 id가 있어도 그 노드만 열고,
자식은 알아서 열리는 구조가 아님.
내가 기대했던
“부모가 열리면 자식도 재귀적으로 같이 열려야 한다”
이 로직 자체가 없음.
5. 이건 그냥 내가 방법을 모르는 게 아니라, 애초에 구조가 안 되는 거다
처음에는 내가 뭔가 잘못 쓴 줄 알았는데,
찾아보니까 Vuetify 3에서는
Vuetify 2에 있던 updateAll 같은 API도 없음.
this.treeview.updateAll is not a function
TypeError: this.treeview.updateAll is not a function
이 에러까지 보고
아… 이거는 구조적으로 애초에 전체 열기/닫기가 불가능하구나
확신이 들었다.
6. 결국 재귀 컴포넌트로 갈아탔다
억지로 우회해서 v-treeview를 살릴까 고민도 해봤지만
이런 뻔한 기능 하나 못 하면서 쓰기에는 애매해서
깔끔하게 재귀 컴포넌트로 직접 구현하는 쪽으로 방향을 바꿨다.
7. 최종 구현 코드
부모: MediaCategoryComponent.vue
<ProductCategoryNode
v-for="item in mediaItemCategoryVo"
:key="item.id"
:node="item"
v-model:expanded="expandedItems"
/>
expandAll() {
this.expandedItems = this.collectAllParentIds(this.mediaItemCategoryVo);
}
collapseAll() {
this.expandedItems = [];
}
자식: ProductCategoryNode.vue
@Prop({ required: true }) readonly node!: any;
@Prop({ required: true }) readonly expanded!: string[];
get isExpanded(): boolean {
return this.expanded.includes(String(this.node.id));
}
@Emit('update:expanded')
toggleExpand(): string[] {
if (!this.node.children?.length) {
return this.expanded; // leaf 노드는 열지 않음
}
const id = String(this.node.id);
const expandedCopy = [...this.expanded];
return this.isExpanded
? expandedCopy.filter(item => item !== id)
: [...expandedCopy, id];
}
마무리 (배운 점)
이번에 느낀 건
“왜 안 되지?“ 라는 고민보다 “이 구조에서 되는 기능인가?“ 를 먼저 확인해야 한다.
라이브러리를 쓰면서
막히는 포인트가 기능이 없는 건지, 내가 방법을 모르는 건지
구분 못 하고 오래 헤매면 시간만 버린다.
애초에 구조적으로 불가능한 걸 억지로 해결하려다 시간 낭비했는데,
결국 재귀 컴포넌트로 구현하니까
커스터마이징도 훨씬 자유롭고 마음이 편해졌다.
다음에는…
기능이 안 될 때는 구조부터 확인하자.
안 되는 걸 붙잡고 있는 시간보다 방향 전환이 빠르다.
'개발 > Vue' 카테고리의 다른 글
Nuxt3에서 useFetch 에러 발생과 $fetch로 해결한 사례 (0) | 2025.06.17 |
---|---|
[Vuetify] v-treeview 트리뷰 구현 중 겪은 시행착오 정리 (Labs 컴포넌트 사용 시 주의사항) (0) | 2025.04.17 |
[Vuetify] 사용자 관리 테이블 구성 가이드 (0) | 2025.04.03 |