Skip to content
API 风格:

Table 테이블 ​

열 설정형 테이블입니다. 정렬, 고정 열, 얼룩무늬 줄 무늬, 셀 슬롯을 지원하며, 대용량 데이터를 위한 가상 스크롤도 지원합니다 (core 순수 함수로 가시 창을 계산, 자체 구현이며 라이브러리를 사용하지 않습니다).

기본 사용법 ​

기본 사용법
이름
직무
도시
심연
아키텍처
항저우
소만
프런트엔드
쑤저우
육리
디자인
베이징
vue
<script setup>
const cols = [
  { prop: 'name', label: '이름', width: 140 },
  { prop: 'role', label: '직무' },
  { prop: 'city', label: '도시', width: 120 },
]
const rows = [
  { id: 1, name: '심연', role: '아키텍처', city: '항저우' },
  { id: 2, name: '소만', role: '프런트엔드', city: '쑤저우' },
  { id: 3, name: '육리', role: '디자인', city: '베이징' },
]
</script>

<template>
  <LhTable :data="rows" :columns="cols" />
</template>
vue
<script>
export default {
  data() {
    return {
      cols: [
        { prop: 'name', label: '이름', width: 140 },
        { prop: 'role', label: '직무' },
        { prop: 'city', label: '도시', width: 120 },
      ],
      rows: [
        { id: 1, name: '심연', role: '아키텍처', city: '항저우' },
        { id: 2, name: '소만', role: '프런트엔드', city: '쑤저우' },
        { id: 3, name: '육리', role: '디자인', city: '베이징' },
      ],
    }
  },
}
</script>

<template>
  <LhTable :data="rows" :columns="cols" />
</template>

정렬과 고정 열 ​

정렬 + 고정 열 + 얼룩무늬
이름
직무
도시
심연
34
아키텍처
항저우
소만
28
프런트엔드
쑤저우
육리
31
디자인
베이징
vue
<script setup>
const sortCols = [
  { prop: 'name', label: '이름', width: 140, fixed: 'left' },
  { prop: 'age', label: '나이', width: 100, sortable: true, align: 'center' },
  { prop: 'role', label: '직무' },
  { prop: 'city', label: '도시', width: 120 },
]
</script>

<template>
  <LhTable :data="rows" :columns="sortCols" stripe border size="small" />
</template>
vue
<script>
export default {
  data() {
    return {
      sortCols: [
        { prop: 'name', label: '이름', width: 140, fixed: 'left' },
        { prop: 'age', label: '나이', width: 100, sortable: true, align: 'center' },
        { prop: 'role', label: '직무' },
        { prop: 'city', label: '도시', width: 120 },
      ],
    }
  },
}
</script>

<template>
  <LhTable :data="rows" :columns="sortCols" stripe border size="small" />
</template>

가상 스크롤 (대용량 데이터) ​

height를 설정하면 활성화됩니다. 가시 창 내의 행만 렌더링하며, rowHeight는 실제 행 높이와 일치해야 합니다.

가상 스크롤 1000행
이름
직무
도시
행 1
아키텍처
항저우
행 2
프런트엔드
항저우
행 3
아키텍처
항저우
행 4
프런트엔드
항저우
행 5
아키텍처
항저우
행 6
프런트엔드
항저우
vue
<script setup lang="ts">
const cols = [
  { prop: 'name', label: '이름', width: 140 },
  { prop: 'role', label: '직무' },
  { prop: 'city', label: '도시', width: 120 },
]

const bigRows = Array.from({ length: 1000 }, (_, i) => ({
  id: i + 1,
  name: `행 ${i + 1}`,
  role: i % 2 === 0 ? '아키텍처' : '프런트엔드',
  city: '항저우',
}))
</script>

<template>
  <LhTable :data="bigRows" :columns="cols" :height="240" :row-height="48" />
</template>
vue
<script>
export default {
  data() {
    return {
      cols: [
        { prop: 'name', label: '이름', width: 140 },
        { prop: 'role', label: '직무' },
        { prop: 'city', label: '도시', width: 120 },
      ],
      bigRows: Array.from({ length: 1000 }, (_, i) => ({
        id: i + 1,
        name: `행 ${i + 1}`,
        role: i % 2 === 0 ? '아키텍처' : '프런트엔드',
        city: '항저우',
      })),
    }
  },
}
</script>

<template>
  <LhTable :data="bigRows" :columns="cols" :height="240" :row-height="48" />
</template>

커스텀 셀 ​

슬롯 커스텀
이름
직무
심연
아키텍처
소만
프런트엔드
육리
디자인
vue
<script setup lang="ts">
const rows = [
  { id: 1, name: '심연', age: 34, role: '아키텍처', city: '항저우' },
  { id: 2, name: '소만', age: 28, role: '프런트엔드', city: '쑤저우' },
  { id: 3, name: '육리', age: 31, role: '디자인', city: '베이징' },
]
const slotCols = [
  { prop: 'name', label: '이름', width: 140 },
  { prop: 'role', label: '직무', slot: 'role' },
]
</script>

<template>
  <!-- 직무 열 슬롯 커스텀 -->
  <LhTable :data="rows" :columns="slotCols">
    <template #role="{ value }">
      <LhTag :type="value === '아키텍처' ? 'primary' : 'info'">{{ value }}</LhTag>
    </template>
  </LhTable>
</template>
vue
<script>
export default {
  data() {
    return {
      rows: [
        { id: 1, name: '심연', age: 34, role: '아키텍처', city: '항저우' },
        { id: 2, name: '소만', age: 28, role: '프런트엔드', city: '쑤저우' },
        { id: 3, name: '육리', age: 31, role: '디자인', city: '베이징' },
      ],
      slotCols: [
        { prop: 'name', label: '이름', width: 140 },
        { prop: 'role', label: '직무', slot: 'role' },
      ],
    }
  },
}
</script>

<template>
  <!-- 직무 열 슬롯 커스텀 -->
  <LhTable :data="rows" :columns="slotCols">
    <template #role="{ value }">
      <LhTag :type="value === '아키텍처' ? 'primary' : 'info'">{{ value }}</LhTag>
    </template>
  </LhTable>
</template>

다단 헤더 ​

열 설정에 children을 중첩하면 다단 헤더가 되며, 무한히 중첩할 수 있습니다. 표 본체는 잎 열에 맞춰 정렬됩니다.

2단 헤더
이름
프로젝트 정보
직무
도시
심연
아키텍처
항저우
34
소만
프런트엔드
쑤저우
28
육리
디자인
베이징
31
vue
<script setup>
const nestedCols = [
  { prop: 'name', label: '이름', width: 140, fixed: 'left' },
  {
    label: '프로젝트 정보',
    children: [
      { prop: 'role', label: '직무' },
      { prop: 'city', label: '도시', width: 120 },
    ],
  },
  { prop: 'age', label: '나이', sortable: true, align: 'center' },
]
</script>

<template>
  <LhTable :data="rows" :columns="nestedCols" border />
</template>
vue
<script>
export default {
  data() {
    return {
      nestedCols: [
        { prop: 'name', label: '이름', width: 140, fixed: 'left' },
        {
          label: '프로젝트 정보',
          children: [
            { prop: 'role', label: '직무' },
            { prop: 'city', label: '도시', width: 120 },
          ],
        },
        { prop: 'age', label: '나이', sortable: true, align: 'center' },
      ],
    }
  },
}
</script>

<template>
  <LhTable :data="rows" :columns="nestedCols" border />
</template>

필터 ​

열 설정에 filters를 지정하면 헤더에 필터 트리거가 나타납니다. 옵션을 선택하고「확인」을 누르면 적용되고,「초기화」를 누르면 비워집니다. filterMultiple: false는 단일 선택이며, filterMethod로 판정을 커스터마이징할 수 있습니다. 제어되는 사용법은 아래 예시를 참조하세요.

열 필터 (제어되는 방식)
이름
직무
심연
아키텍처
소만
프런트엔드
육리
디자인

현재 필터: {}

vue
<script setup>
import { ref } from 'vue'

const filteredValues = ref({})

const filterCols = [
  { prop: 'name', label: '이름', width: 140 },
  {
    prop: 'role', label: '직무', filters: [
      { text: '아키텍처', value: '아키텍처' },
      { text: '프런트엔드', value: '프런트엔드' },
      { text: '디자인', value: '디자인' },
    ],
  },
]

function onFilterChange({ prop, values }) {
  console.log('filterChange', prop, values)
}
</script>

<template>
  <!-- 제어되는 필터 -->
  <LhTable
    :data="rows"
    :columns="filterCols"
    v-model:filtered-values="filteredValues"
    @filter-change="onFilterChange"
  />
</template>
vue
<script>
export default {
  data() {
    return {
      filteredValues: {},
      filterCols: [
        { prop: 'name', label: '이름', width: 140 },
        {
          prop: 'role', label: '직무', filters: [
            { text: '아키텍처', value: '아키텍처' },
            { text: '프런트엔드', value: '프런트엔드' },
            { text: '디자인', value: '디자인' },
          ],
        },
      ],
    }
  },
  methods: {
    onFilterChange({ prop, values }) {
      console.log('filterChange', prop, values)
    },
  },
}
</script>

<template>
  <!-- 제어되는 필터 -->
  <LhTable
    :data="rows"
    :columns="filterCols"
    v-model:filtered-values="filteredValues"
    @filter-change="onFilterChange"
  />
</template>

열 너비 드래그 ​

resizable (마스터 스위치)를 켜고 열에 resizable: true를 표시합니다 (잎 열이며 숫자 width여야 함). 헤더 오른쪽 가장자리에 드래그 핸들이 나타나며, 포커스 후 ←/→로 매번 ±10px씩 조정할 수 있고 드래그 하한은 40px입니다.

드래그로 열 너비 조정
이름
직무
도시
심연
아키텍처
항저우
소만
프런트엔드
쑤저우
육리
디자인
베이징

최근 조정: (조정 없음)

vue
<script setup>
import { ref } from 'vue'

const resizeLog = ref('(조정 없음)')

const resizeCols = [
  { prop: 'name', label: '이름', width: 140, resizable: true, fixed: 'left' },
  { prop: 'role', label: '직무' },
  { prop: 'city', label: '도시', width: 120, resizable: true },
]

function onColumnResize({ prop, width }) {
  resizeLog.value = `${prop} → ${width}px`
}
</script>

<template>
  <!-- 열 너비 드래그 -->
  <LhTable :data="rows" :columns="resizeCols" resizable border @column-resize="onColumnResize" />
</template>
vue
<script>
export default {
  data() {
    return {
      resizeLog: '(조정 없음)',
      resizeCols: [
        { prop: 'name', label: '이름', width: 140, resizable: true, fixed: 'left' },
        { prop: 'role', label: '직무' },
        { prop: 'city', label: '도시', width: 120, resizable: true },
      ],
    }
  },
  methods: {
    onColumnResize({ prop, width }) {
      this.resizeLog = `${prop} → ${width}px`
    },
  },
}
</script>

<template>
  <!-- 열 너비 드래그 -->
  <LhTable :data="rows" :columns="resizeCols" resizable border @column-resize="onColumnResize" />
</template>

행 키, 최대 높이, 기본 정렬 ​

row-key는 행의 고유 키를 지정합니다 (권장). max-height로 높이를 제한하면 헤더가 상단에 고정되고 표 본체가 스크롤됩니다. sorter는 커스텀 정렬 함수, default-sort-prop / default-sort-order는 초기 정렬, default-filtered-values는 초기 필터를 설정합니다.

rowKey / maxHeight / 기본 정렬
이름
직무
심연
34
아키텍처
육리
31
디자인
소만
28
프런트엔드
vue
<script setup>
const sorterCols = [
  { prop: 'name', label: '이름', width: 140 },
  { prop: 'age', label: '나이', sortable: true, sorter: (a, b) => a.age - b.age },
  { prop: 'role', label: '직무' },
]
</script>

<template>
  <LhTable
    :data="rows"
    :columns="sorterCols"
    row-key="name"
    max-height="200"
    default-sort-prop="age"
    default-sort-order="desc"
  />
</template>
vue
<script>
export default {
  data() {
    return {
      sorterCols: [
        { prop: 'name', label: '이름', width: 140 },
        { prop: 'age', label: '나이', sortable: true, sorter: (a, b) => a.age - b.age },
        { prop: 'role', label: '직무' },
      ],
    }
  },
}
</script>

<template>
  <LhTable
    :data="rows"
    :columns="sorterCols"
    row-key="name"
    max-height="200"
    default-sort-prop="age"
    default-sort-order="desc"
  />
</template>

API ​

LhTable Props ​

프로퍼티설명타입기본값
data행 데이터[LhTableRow](#type-LhTableRow)[]—
columns열 설정[LhTableColumn](#type-LhTableColumn)[]—
height표 본체 높이 (설정하면 가상 스크롤 활성화, 숫자는 px)string | number—
maxHeight가상 스크롤 모드가 아닐 때의 최대 높이string | number—
rowHeight가상 스크롤 행 높이 (실제 행 높이와 일치해야 함)number[LH_TABLE_ROW_HEIGHT](#const-LH_TABLE_ROW_HEIGHT)
stripe얼룩무늬 줄 무늬booleanfalse
border셀 테두리booleanfalse
size크기"small" | "default" | "large"'default'
rowKey행 키 필드명string'id'
defaultSortProp기본 정렬 열string—
defaultSortOrder기본 정렬 방향"asc" | "desc"—
sorter커스텀 정렬 함수(a: LhTableRow, b: LhTableRow) => number—
resizable열 너비 드래그 활성화 (열 수준 resizable 설정도 필요)booleanfalse
defaultFilteredValues기본 필터 상태 (제어되지 않음, 키는 열 prop){ [x: string]: (string | number)[]; }() => ({})
filteredValues제어되는 필터 상태 (키는 열 prop){ [x: string]: (string | number)[]; }—
sxsx 빠른 레이아웃 속성LhSxProps—

Events ​

이벤트명설명콜백 매개변수
sortChange정렬이 변경될 때 트리거 (열을 바꾸면 asc부터 다시 시작, 같은 열은 asc → desc → 취소 순환)(payload: { prop: string, order: 'asc' | 'desc' | undefined })
rowClick행을 클릭할 때 트리거(row: LhTableRow, index: number)
filterChange필터「확인」또는「초기화」제출 후 트리거(payload: { prop: string, values: (string | number)[] })
update:filteredValues필터 상태가 변경될 때 트리거 (v-model:filtered-values)(values: Record<string, (string | number)[]>)
columnResize열 너비 조정 완료 후 트리거 (드래그 종료 또는 키보드 ←/→)(payload: { prop: string, width: number })

슬롯: empty, <column.slot> (범위 매개변수 { row, column, index, value }).

LhTableColumn ​

필드설명타입
prop데이터 필드명string
label열 제목string
width열 너비 (숫자는 px, 설정하지 않으면 균등 분배)number | string
align정렬'left' | 'center' | 'right'
fixed고정 열 (width 필요)'left' | 'right'
sortable정렬 가능boolean
slot커스텀 셀 슬롯명string
formatter값 형식화(row, value) => string
children하위 열 (설정하면 다단 헤더)LhTableColumn[]
filters열 필터 옵션 (잎 열만 유효)LhTableFilterOption[]
filterMethod커스텀 필터 판정 (기본은 prop 완전 일치 비교)(value, row) => boolean
filterMultiple필터 패널 다중 선택 여부boolean
resizable이 열의 너비 드래그 가능 (숫자 width인 잎 열이어야 함)boolean

무장애 ​

  • role="grid" + aria-rowcount / aria-colcount, 행은 row, 열 헤더는 columnheader, 셀은 gridcell
  • 정렬 열 헤더는 aria-sort (none / ascending / descending)를 노출하며, 정렬 트리거는 실제 <button>입니다
  • 고정 열은 CSS position: sticky를 사용합니다 (자체 흡착 계산을 하지 않음)
  • 필터 트리거는 aria-haspopup + aria-expanded이며, 패널은 role="group"입니다
  • 열 너비 핸들은 role="separator"이며 포커스할 수 있고 ←/→ 키로 ±10px 조정할 수 있습니다

타입 정의 ​

이 컴포넌트가 외부로 노출하는 TypeScript 타입 구조는 다음과 같습니다 (packages/components/table/src/types.ts와 @lh-ui/share/core/* 소스에서 자동 추출, 각 항목을 펼쳐 전체 정의를 확인하세요. 프로퍼티 설명은 위쪽 Props 표를 참조하세요):

LhTableProps
ts
export interface LhTableProps {
  /** 행 데이터 */
  data: LhTableRow[]
  /** 열 설정 */
  columns: LhTableColumn[]
  /** 표 본체 높이 (설정하면 가상 스크롤 활성화, 숫자는 px) */
  height?: number | string
  /** 가상 스크롤 모드가 아닐 때의 최대 높이 */
  maxHeight?: number | string
  /** 가상 스크롤 행 높이 (실제 행 높이와 일치해야 함) */
  rowHeight?: number
  /** 얼룩무늬 줄 무늬 */
  stripe?: boolean
  /** 셀 테두리 */
  border?: boolean
  /** 크기 */
  size?: 'small' | 'default' | 'large'
  /** 행 키 필드명 */
  rowKey?: string
  /** 기본 정렬 열 */
  defaultSortProp?: string
  /** 기본 정렬 방향 */
  defaultSortOrder?: LhTableSortOrder
  /** 커스텀 정렬 함수 */
  sorter?: (a: LhTableRow, b: LhTableRow) => number
  /** 열 너비 드래그 활성화 (열 수준 resizable 설정도 필요, 열은 숫자 width여야 함) */
  resizable?: boolean
  /** 기본 필터 상태 (제어되지 않음, 키는 열 prop) */
  defaultFilteredValues?: Record<string, (string | number)[]>
  /** 제어되는 필터 상태 (키는 열 prop) */
  filteredValues?: Record<string, (string | number)[]>
  /** sx 빠른 레이아웃 속성 */
  sx?: LhSxProps
}
LhTableSortOrder (출처: @lh-ui/share/core/table)
ts
/** 정렬 방향 */
export type LhTableSortOrder = 'asc' | 'desc'
LhTableRow (출처: @lh-ui/share/core/table)
ts
/** 행 데이터 타입 */
export type LhTableRow = Record<string, any>
LhTableColumn (출처: @lh-ui/share/core/table)
ts
/** 열 설정 */
export interface LhTableColumn {
  /** 데이터 필드명 */
  prop: string
  /** 열 제목 */
  label: string
  /** 열 너비 (숫자는 px) */
  width?: number | string
  /** 정렬 */
  align?: LhTableAlign
  /** 고정 열 (width 필요) */
  fixed?: 'left' | 'right'
  /** 정렬 가능 */
  sortable?: boolean
  /** 커스텀 셀 슬롯명 */
  slot?: string
  /** 값 형식화 */
  formatter?: (row: LhTableRow, value: any) => string
  /** 하위 열 (설정하면 다단 헤더, 무한 중첩 가능) */
  children?: LhTableColumn[]
  /** 열 필터 옵션 (잎 열만 유효) */
  filters?: LhTableFilterOption[]
  /** 커스텀 필터 판정 (기본은 prop 완전 일치 비교) */
  filterMethod?: (value: string | number, row: LhTableRow) => boolean
  /** 필터 패널 다중 선택 여부 (기본 true) */
  filterMultiple?: boolean
  /** 이 열의 너비 드래그 가능 (숫자 width인 잎 열이어야 함) */
  resizable?: boolean
}
LhTableFilterOption (출처: @lh-ui/share/core/table)
ts
/** 필터 옵션 */
export interface LhTableFilterOption {
  /** 옵션 문구 */
  text: string
  /** 옵션 값 (filterMethod 비교에 참여) */
  value: string | number
}
LhTableAlign (출처: @lh-ui/share/core/table)
ts
/** 셀 정렬 */
export type LhTableAlign = 'left' | 'center' | 'right'

상수 ​

이 컴포넌트에서 사용하는 공개 상수는 다음과 같습니다 (Props 표의 「타입 / 기본값」 열에서 동일한 이름의 항목을 클릭하면 이곳으로 이동합니다):

LH_TABLE_ROW_HEIGHT (출처: @lh-ui/share/core/table)
ts
/** 가상 스크롤 기본 행 높이 */
export const LH_TABLE_ROW_HEIGHT = 48
LH_TABLE_OVERSCAN (출처: @lh-ui/share/core/table)
ts
/** 창 상하에 각각 몇 행 더 렌더링 (스크롤 빈 화면 버퍼) */
export const LH_TABLE_OVERSCAN = 3
LH_TABLE_MIN_COL_WIDTH (출처: @lh-ui/share/core/table)
ts
// ============================================================================
// 열 너비 드래그
// ============================================================================

/** 드래그 하한 (px), 이보다 좁아지면 셀 콘텐츠를 담을 수 없음 */
export const LH_TABLE_MIN_COL_WIDTH = 40

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