Skip to content

Tree

@jennifersoft/apm-componentsTree 컴포넌트 문서입니다. 이 문서는 @jennifersoft/apm-components@1.4.1 기준으로 정리했습니다.

계층 데이터를 가상 스크롤로 렌더하는 저수준 트리입니다. 검색·전체선택 등 부가 기능이 필요하면 TreeSelector를 사용하세요.

특징

  • @vueuse/componentsUseVirtualList 기반 가상 스크롤 (행 높이 24px 고정)
  • 제네릭 <T>로 노드에 임의 데이터를 실을 수 있음
  • 접힘·선택 상태를 소유하지 않음 — 이벤트만 emit하고 갱신은 호출자가 함

인터랙티브 데모

Props

Prop타입기본값설명
widthnumber— (필수)폭(px). 0이면 100%
heightnumber— (필수)높이(px). 가상 스크롤 계산에 사용
dataTreeNode<T>[]— (필수)트리 데이터
checkedKeysstring[][]외부에서 지정하는 선택 키 목록
typescript
interface TreeNode<T> {
    key: string;
    label: string;
    children: TreeNode<T>[];
    fold?: boolean;      // true면 접힘
    check?: boolean;     // 노드 자체의 선택 상태
    disable?: boolean;
    icon?: IconTypes;    // @jennifersoft/vue-components-v2의 ICON_TYPE
    data?: T;            // 임의 페이로드
}

children필수 필드입니다. 리프 노드도 빈 배열을 넣어야 합니다.

이벤트

typescript
emit('change-node-fold', key: string)
emit('click-node-label', args: [key: string, checked: boolean])

click-node-label의 페이로드가 두 인자가 아니라 길이 2의 튜플 하나임에 주의하세요.

typescript
function onLabelClick([key, checked]: [string, boolean]) { /* ... */ }

선택 상태 결정 규칙

checkedKeys와 노드의 check 필드가 함께 작동하며, 우선순위가 있습니다.

checkedKeys선택 여부
비어 있음 ([])노드의 check 값만 사용
하나 이상checkedKeys에 포함되거나 또는 checktrue

NOTE

checkedKeys가 비어 있지 않을 때는 OR 조건입니다. checkedKeys로 선택을 완전히 통제하려면 데이터의 check를 모두 false로 두세요.

슬롯

슬롯설명
icon노드 아이콘 영역을 대체. 내부 Node의 slot props를 그대로 전달

사용 예제

vue
<script setup lang="ts">
import { ref } from 'vue';
import { Tree } from '@jennifersoft/apm-components';
import type { TreeNode } from '@jennifersoft/apm-components';

interface Payload {
    domainId: number;
}

const data = ref<TreeNode<Payload>[]>([
    {
        key: 'domain-1',
        label: 'OrderService',
        fold: false,
        children: [
            { key: 'inst-1', label: 'order-01', children: [], data: { domainId: 1 } },
            { key: 'inst-2', label: 'order-02', children: [], data: { domainId: 1 } },
        ],
    },
]);

const checkedKeys = ref<string[]>([]);

// 접힘 상태는 호출자가 갱신한다
function onFoldChange(key: string) {
    toggleFold(data.value, key);
}

function onLabelClick([key, checked]: [string, boolean]) {
    checkedKeys.value = checked
        ? [...checkedKeys.value, key]
        : checkedKeys.value.filter((item) => item !== key);
}
</script>

<template>
    <Tree
        :width="0"
        :height="320"
        :data="data"
        :checked-keys="checkedKeys"
        @change-node-fold="onFoldChange"
        @click-node-label="onLabelClick"
    >
        <template #icon="slotProps">
            <CustomIcon v-bind="slotProps" />
        </template>
    </Tree>
</template>

관련 타입

typescript
/** 평탄화 후 렌더링에 사용하는 형태 */
interface TreeRenderingData<T = unknown>
    extends Omit<TreeNode<T>, 'children' | 'data'> {
    depth: number;
    leaf: boolean;
}

interface TreeCacheMap<T = unknown> {
    [key: string]: {
        parent: string | null;
        data: TreeRenderingData<T>;
    };
}

내부적으로 convertRenderingData()가 트리를 평탄화하면서 depthleaf를 계산합니다. 접힌 노드의 자식은 목록에서 제외되므로 가상 스크롤 길이가 자동으로 줄어듭니다.

주의사항

  • 행 높이는 24px 고정입니다. CSS로 바꾸면 가상 스크롤 위치 계산이 어긋납니다.
  • height를 주지 않거나 0으로 두면 아무것도 보이지 않습니다. 컨테이너 높이를 계산해 넘기세요.
  • 접힘·선택은 컴포넌트가 관리하지 않습니다. emit을 받아 data 또는 checkedKeys를 갱신해야 화면이 바뀝니다.
  • 검색, 전체 선택, 부분 선택(indeterminate)이 필요하면 TreeSelector를 사용하세요.