Skip to content
API 风格:

Tree 트리 ​

플랫 데이터를 트리로 변환하거나 선택을 전파하는 작업은 모두 core 순수 함수에서 처리하며, 컴포넌트는 렌더링과 키보드 조작만 담당합니다.

기본 사용법 ​

기본 사용법
프론트엔드
Vue
React
데브옵스
vue
<script setup>
import { ref } from 'vue'
const treeData = [
  { key: 'a', label: '프론트엔드', children: [
    { key: 'a-1', label: 'Vue' },
    { key: 'a-2', label: 'React' },
  ] },
  { key: 'b', label: '백엔드', children: [{ key: 'b-1', label: 'Node' }] },
  { key: 'c', label: '데브옵스', disabled: true },
]
</script>

<template>
  <!-- 기본 사용법 -->
  <LhTree :data="treeData" :default-expanded-keys="['a']" />
</template>
vue
<script>
export default {
  data() {
    return {
      treeData: [
        { key: 'a', label: '프론트엔드', children: [
          { key: 'a-1', label: 'Vue' },
          { key: 'a-2', label: 'React' },
        ] },
        { key: 'b', label: '백엔드', children: [{ key: 'b-1', label: 'Node' }] },
        { key: 'c', label: '데브옵스', disabled: true },
      ],
    }
  },
}
</script>

<template>
  <!-- 기본 사용법 -->
  <LhTree :data="treeData" :default-expanded-keys="['a']" />
</template>

체크 가능 (계단식) ​

상위 노드를 체크하면 모든 하위 노드로 계단식 전파됩니다. 하위 노드를 모두 선택하면 상위 노드도 자동으로 선택되며, 일부만 선택된 경우 중간 선택 상태 (indeterminate)로 표시됩니다.

체크 계단식 전파
프론트엔드
Vue
React
백엔드
Node
데브옵스
vue
<script setup lang="ts">
const treeData = [
  { key: 'a', label: '프론트엔드', children: [
    { key: 'a-1', label: 'Vue' },
    { key: 'a-2', label: 'React' },
  ] },
  { key: 'b', label: '백엔드', children: [{ key: 'b-1', label: 'Node' }] },
  { key: 'c', label: '데브옵스', disabled: true },
]
</script>

<template>
  <!-- 체크 계단식 전파 -->
  <LhTree :data="treeData" show-checkbox :default-expanded-keys="['a', 'b']" />
</template>
vue
<script>
export default {
  data() {
    return {
      treeData: [
        { key: 'a', label: '프론트엔드', children: [
          { key: 'a-1', label: 'Vue' },
          { key: 'a-2', label: 'React' },
        ] },
        { key: 'b', label: '백엔드', children: [{ key: 'b-1', label: 'Node' }] },
        { key: 'c', label: '데브옵스', disabled: true },
      ],
    }
  },
}
</script>

<template>
  <!-- 체크 계단식 전파 -->
  <LhTree :data="treeData" show-checkbox :default-expanded-keys="['a', 'b']" />
</template>

지연 로딩 ​

lazy를 켜면 로드되지 않은 노드를 펼칠 때 load 콜백이 호출됩니다 (resolve로 자식 노드를 전달하며 펼치고, reject로 취소합니다). 로딩 중에는 화살표가 회전하는 애니메이션으로 표시됩니다. 로드 가능한 노드는 isLeaf: false를 명시적으로 선언해야 합니다 (그렇지 않으면 children이 없는 노드가 리프로 추론되어 전개 화살표가 표시되지 않습니다). 로드된 자식 노드가 여전히 로드 대상 브랜치인 경우에도 마찬가지로 isLeaf: false를 선언하고, 실제 리프는 isLeaf: true로 선언합니다.

지연 로딩
vue
<script setup>
import { ref } from 'vue'

const lazyData = ref([
  { key: 'lazy', label: '클릭하여 로드', isLeaf: false },
])

function loadNodes(node, resolve, reject) {
  // 비동기 요청 시뮬레이션
  setTimeout(() => {
    if (node.key === 'lazy') {
      resolve([
        { key: 'lazy-1', label: '자식 노드 1', isLeaf: true },
        { key: 'lazy-2', label: '자식 노드 2', isLeaf: true },
      ])
    } else {
      reject(new Error('nothing to load'))
    }
  }, 600)
}
</script>

<template>
  <!-- 지연 로딩 -->
  <LhTree :data="lazyData" lazy :load="loadNodes" />
</template>
vue
<script>
export default {
  data() {
    return {
      lazyData: [{ key: 'lazy', label: '클릭하여 로드', isLeaf: false }],
    }
  },
  methods: {
    // 비동기 요청 시뮬레이션
    loadNodes(node, resolve, reject) {
      setTimeout(() => {
        if (node.key === 'lazy') {
          resolve([
            { key: 'lazy-1', label: '자식 노드 1', isLeaf: true },
            { key: 'lazy-2', label: '자식 노드 2', isLeaf: true },
          ])
        } else {
          reject(new Error('nothing to load'))
        }
      }, 600)
    },
  },
}
</script>

<template>
  <!-- 지연 로딩 -->
  <LhTree :data="lazyData" lazy :load="loadNodes" />
</template>

부모-자식 연동 해제 (checkStrictly) ​

checkStrictly를 켜면 체크가 노드 자신에게만 적용됩니다. 하위 노드로 계단식 전파되지 않고, 상위 노드로 거슬러 올라가지 않으며, 중간 선택 상태도 없습니다.

checkStrictly
프론트엔드
Vue
React
백엔드
Node
데브옵스
vue
<script setup lang="ts">
const treeData = [
  { key: 'a', label: '프론트엔드', children: [
    { key: 'a-1', label: 'Vue' },
    { key: 'a-2', label: 'React' },
  ] },
  { key: 'b', label: '백엔드', children: [{ key: 'b-1', label: 'Node' }] },
  { key: 'c', label: '데브옵스', disabled: true },
]
</script>

<template>
  <!-- 부모-자식 연동 해제 -->
  <LhTree :data="treeData" show-checkbox check-strictly :default-expanded-keys="['a', 'b']" />
</template>
vue
<script>
export default {
  data() {
    return {
      treeData: [
        { key: 'a', label: '프론트엔드', children: [
          { key: 'a-1', label: 'Vue' },
          { key: 'a-2', label: 'React' },
        ] },
        { key: 'b', label: '백엔드', children: [{ key: 'b-1', label: 'Node' }] },
        { key: 'c', label: '데브옵스', disabled: true },
      ],
    }
  },
}
</script>

<template>
  <!-- 부모-자식 연동 해제 -->
  <LhTree :data="treeData" show-checkbox check-strictly :default-expanded-keys="['a', 'b']" />
</template>

선택과 제어형 키 값 ​

multiple은 다중 선택을 허용합니다. expand-on-click-node는 노드 클릭 시 펼치거나 접습니다. default-selected-keys / v-model:selected-keys로 선택을 제어하고, default-checked-keys / v-model:checked-keys로 체크를 제어하며, default-expanded-keys / v-model:expanded-keys로 전개를 제어합니다. disabled는 트리 전체를 비활성화합니다.

제어형 선택과 체크
디자인
색채
서체
엔지니어링

선택: [ "x" ] | 체크: [ "x-1" ]

트리 전체 비활성화
엔지니어링
vue
<script setup>
import { ref } from 'vue'
const selKeys = ref(['x'])
const chkKeys = ref(['x-1'])
const ctrlData = [
  { key: 'x', label: '디자인', children: [
    { key: 'x-1', label: '색채' },
    { key: 'x-2', label: '서체' },
  ] },
  { key: 'y', label: '엔지니어링' },
]
</script>

<template>
  <!-- 제어형 선택과 체크 -->
  <LhTree
    v-model:selected-keys="selKeys"
    v-model:checked-keys="chkKeys"
    :data="ctrlData"
    multiple
    expand-on-click-node
    :default-expanded-keys="['x']"
    :default-selected-keys="['x']"
    :default-checked-keys="['x-1']"
  />
  <p class="demo-tree-log">선택: {{ selKeys }} | 체크: {{ chkKeys }}</p>
  <!-- 트리 전체 비활성화 -->
  <LhTree :data="ctrlData" disabled />
</template>
vue
<script>
export default {
  data() {
    return {
      selKeys: ['x'],
      chkKeys: ['x-1'],
      ctrlData: [
        { key: 'x', label: '디자인', children: [
          { key: 'x-1', label: '색채' },
          { key: 'x-2', label: '서체' },
        ] },
        { key: 'y', label: '엔지니어링' },
      ],
    }
  },
}
</script>

<template>
  <!-- 제어형 선택과 체크 -->
  <LhTree
    v-model:selected-keys="selKeys"
    v-model:checked-keys="chkKeys"
    :data="ctrlData"
    multiple
    expand-on-click-node
    :default-expanded-keys="['x']"
    :default-selected-keys="['x']"
    :default-checked-keys="['x-1']"
  />
  <p class="demo-tree-log">선택: {{ selKeys }} | 체크: {{ chkKeys }}</p>
  <!-- 트리 전체 비활성화 -->
  <LhTree :data="ctrlData" disabled />
</template>

API ​

LhTree Props ​

속성명설명타입기본값
data트리 데이터[LhTreeNode](#type-LhTreeNode)[]—
showCheckbox체크박스 표시booleanfalse
multiple다중 선택 (노드 클릭 시 교체 대신 선택을 전환)booleanfalse
expandOnClickNode리프가 아닌 노드 클릭 시 펼치기/접기booleantrue
defaultExpandedKeys기본 펼침 키 (비제어형)string[]() => []
expandedKeys제어형 펼침 키string[]—
defaultSelectedKeys기본 선택 키 (비제어형)string[]() => []
selectedKeys제어형 선택 키string[]—
defaultCheckedKeys기본 체크 키 (비제어형)string[]() => []
checkedKeys제어형 체크 키string[]—
checkStrictly부모-자식 체크 연동 해제 (체크가 자신에게만 적용되며, 계단식 전파와 중간 선택 없음)booleanfalse
lazy지연 로딩 모드 (로드되지 않은 노드 펼침 시 load 호출)booleanfalse
load지연 로딩 콜백 (resolve로 자식 노드를 전달하며 펼치고, reject로 취소)(node: LhTreeNode, resolve: (children: LhTreeNode[]) => void, reject: (reason?: unknown) => void) => void—
disabled전체 비활성화booleanfalse
sxsx 빠른 레이아웃 속성LhSxProps—

Events ​

이벤트명설명콜백 매개변수
update:expandedKeys펼침 키 목록이 변경될 때 트리거되며, v-model:expanded-keys와 함께 사용(keys: string[])
update:selectedKeys선택 키 목록이 변경될 때 트리거되며, v-model:selected-keys와 함께 사용(keys: string[])
update:checkedKeys체크 키 목록이 변경될 때 트리거되며, v-model:checked-keys와 함께 사용(keys: string[])
select노드가 클릭되어 선택되거나 선택이 전환된 후 트리거(key: string, node: LhTreeNode)
check체크가 변경된 후 트리거(keys: string[], node: LhTreeNode)
expand노드가 펼쳐지거나 접힌 후 트리거(key: string, expanded: boolean)
nodeLoad지연 로딩 자식 노드가 반환된 후 트리거(node: LhTreeNode, children: LhTreeNode[])

슬롯: node (스코프 { node, level, expanded, selected }), empty.

LhTreeNode ​

필드설명타입
key고유 키string
label표시 텍스트string
children자식 노드LhTreeNode[]
disabled비활성화boolean
isLeaf리프 여부 (전달하지 않으면 children으로부터 추론)boolean

접근성 ​

  • role="tree" + role="treeitem"이며, aria-level / aria-expanded / aria-selected / aria-disabled가 모두 노출됩니다
  • 체크박스는 role="checkbox" + aria-checked입니다 (중간 선택은 mixed)
  • 키보드 (APG tree 패턴): ↑/↓로 항목 간 이동 (비활성화 항목은 건너뛰고 순환), →로 펼치거나 첫 번째 자식으로 진입, ←로 접거나 상위 노드로 복귀, Home/End로 처음과 끝, Enter/Space로 활성화. roving tabindex를 사용합니다

타입 정의 ​

이 컴포넌트가 외부로 내보내는 TypeScript 타입 구조는 다음과 같습니다 (packages/components/tree/src/types.ts와 @lh-ui/share/core/* 소스에서 자동 추출되었습니다. 각 항목을 클릭하여 펼쳐 보세요. 속성 설명은 위쪽 Props 표를 참고하세요):

LhTreeProps
ts
export interface LhTreeProps {
  /** 트리 데이터 */
  data: LhTreeNode[]
  /** 체크박스 표시 */
  showCheckbox?: boolean
  /** 다중 선택 (노드 클릭 시 교체 대신 선택을 전환) */
  multiple?: boolean
  /** 리프가 아닌 노드 클릭 시 펼치기/접기 */
  expandOnClickNode?: boolean
  /** 기본 펼침 키 (비제어형) */
  defaultExpandedKeys?: string[]
  /** 제어형 펼침 키 */
  expandedKeys?: string[]
  /** 기본 선택 키 (비제어형) */
  defaultSelectedKeys?: string[]
  /** 제어형 선택 키 */
  selectedKeys?: string[]
  /** 기본 체크 키 (비제어형) */
  defaultCheckedKeys?: string[]
  /** 제어형 체크 키 */
  checkedKeys?: string[]
  /** 부모-자식 체크 연동 해제 (체크가 노드 자신에게만 적용되며, 계단식 전파와 중간 선택 없음) */
  checkStrictly?: boolean
  /** 지연 로딩 모드: 로드되지 않은 리프가 아닌 노드 펼침 시 load 호출 */
  lazy?: boolean
  /** 지연 로딩 콜백: resolve로 자식 노드를 전달하며 펼치고, reject (또는 에러 발생)로 취소 */
  load?: (
    node: LhTreeNode,
    resolve: (children: LhTreeNode[]) => void,
    reject: (reason?: unknown) => void,
  ) => void
  /** 전체 비활성화 */
  disabled?: boolean
  /** sx 빠른 레이아웃 속성 */
  sx?: LhSxProps
}
LhFlatTreeNode (@lh-ui/share/core/tree에서 제공)
ts
/** 평탄화된 노드 (렌더링용) */
export interface LhFlatTreeNode {
  node: LhTreeNode
  key: string
  level: number
  parentKey: string | null
  /** 조상 체인 (자신은 제외) */
  ancestorKeys: string[]
  /** 리프 여부 */
  isLeaf: boolean
}
LhTreeNode (@lh-ui/share/core/tree에서 제공)
ts
/** 트리 노드 데이터 */
export interface LhTreeNode {
  /** 고유 키 */
  key: string
  /** 표시 텍스트 */
  label: string
  /** 자식 노드 */
  children?: LhTreeNode[]
  /** 비활성화 */
  disabled?: boolean
  /** 리프 여부 (전달하지 않으면 children으로부터 추론) */
  isLeaf?: boolean
}
LhTreeCheckState (@lh-ui/share/core/tree에서 제공)
ts
/** 체크 상태 */
export type LhTreeCheckState = 'checked' | 'indeterminate' | 'unchecked'

국풍의 아름다움으로 코드의 토대를 세웁니다