가상화 테이블

가상화 테이블 (Virtualized Table)

출처: 문서

본문

웹 개발이 진화하면서 테이블 컴포넌트는 특히 대시보드, 데이터 분석에서 가장 인기 있는 컴포넌트가 되었어요. Table V1의 경우 데이터가 1000개만 되어도 성능이 떨어져서 사용하기 불편할 때가 많았죠. Virtualized Table을 사용하면 방대한 양의 데이터도 순식간에 렌더링할 수 있어요.

TIP 이 컴포넌트는 아직 테스트 중이라 사용에 주의가 필요해요. 버그나 이슈를 발견하면 GitHub에 보고해주세요. 또한 이 문서에 언급되지 않은 API도 있는데, 그중 일부는 아직 완전히 개발되지 않아서 여기에 언급하지 않았어요. Virtualized Table이 효율적이긴 하지만 데이터 로드가 너무 크면 네트워크와 메모리 크기가 앱의 병목이 될 수 있어요. Virtualized Table이 모든 문제의 궁극적인 해결책은 아니라는 점을 기억하세요. 데이터를 페이지네이션하고 필터를 추가하는 걸 고려해보세요.

기본 사용법 (Basic Usage)

10개의 열과 1000개의 행이 있는 기본 예시를 렌더링해서 Virtualized Table의 성능을 보여드릴게요.

자동 크기 조절 (Auto Resizer)

테이블에 width와 height 속성을 수동으로 전달하고 싶지 않다면 테이블 컴포넌트를 AutoResizer로 감싸면 돼요. 그러면 width와 height를 자동으로 업데이트해줘요. 브라우저 크기를 조절해서 어떻게 동작하는지 확인해보세요.

TIP AutoResizer의 기본 높이 값이 100%이므로 AutoResizer의 부모 노드가 고정 높이를 가져야 해요. 또는 AutoResizer에 style 속성을 전달해서 높이를 정의할 수도 있어요.

const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
  Array.from({ length }).map((_, columnIndex) => ({
    ...props,
    key: `${prefix}${columnIndex}`,
    dataKey: `${prefix}${columnIndex}`,
    title: `Column ${columnIndex}`,
    width: 150,
  }))

const generateData = (
  columns: ReturnType,
  length = 200,
  prefix = 'row-'
) =>
  Array.from({ length }).map((_, rowIndex) => {
    return columns.reduce(
      (rowData, column, columnIndex) => {
        rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`
        return rowData
      },
      {
        id: `${prefix}${rowIndex}`,
        parentId: null,
      }
    )
  })

const columns = generateColumns(10)
const data = generateData(columns, 200)

셀 렌더러 커스터마이즈 (Customize Cell Renderer)

물론 필요에 따라 테이블 셀을 직접 렌더링할 수도 있어요. 셀을 커스터마이즈하는 간단한 예시를 보여드릴게요.

선택이 있는 테이블 (Table With Selections)

커스텀 셀 렌더러를 사용해서 테이블에 선택 기능을 추가할 수 있어요.

import { ref, unref } from 'vue'
import { ElCheckbox, useLocale } from 'element-plus'

import type { FunctionalComponent } from 'vue'
import type { CheckboxValueType, Column } from 'element-plus'

type SelectionCellProps = {
  value: boolean
  intermediate?: boolean
  ariaLabel?: string
  onChange: (value: CheckboxValueType) => void
}

const { t } = useLocale()

const SelectionCell: FunctionalComponent = ({
  value,
  intermediate = false,
  ariaLabel,
  onChange,
}) => {
  return (

  )
}

const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
  Array.from({ length }).map((_, columnIndex) => ({
    ...props,
    key: `${prefix}${columnIndex}`,
    dataKey: `${prefix}${columnIndex}`,
    title: `Column ${columnIndex}`,
    width: 150,
  }))

const generateData = (
  columns: ReturnType,
  length = 200,
  prefix = 'row-'
) =>
  Array.from({ length }).map((_, rowIndex) => {
    return columns.reduce(
      (rowData, column, columnIndex) => {
        rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`
        return rowData
      },
      {
        id: `${prefix}${rowIndex}`,
        checked: false,
        parentId: null,
      }
    )
  })

const columns: Column[] = generateColumns(10)
columns.unshift({
  key: 'selection',
  width: 50,
  cellRenderer: ({ rowData }) => {
    const onChange = (value: CheckboxValueType) => (rowData.checked = value)
    return (

    )
  },

  headerCellRenderer: () => {
    const _data = unref(data)
    const onChange = (value: CheckboxValueType) =>
      (data.value = _data.map((row) => {
        row.checked = value
        return row
      }))
    const allSelected = _data.every((row) => row.checked)
    const containsChecked = _data.some((row) => row.checked)

    return (

    )
  },
})

const data = ref(generateData(columns, 200))

인라인 편집 (Inline Editing)

위에서 선택 기능으로 보여드린 것처럼, 동일한 방법으로 인라인 편집도 활성화할 수 있어요.

import { ref, withKeys } from 'vue'
import { ElInput } from 'element-plus'

import type { FunctionalComponent } from 'vue'
import type { Column, InputInstance } from 'element-plus'

type SelectionCellProps = {
  value: string
  intermediate?: boolean
  onChange: (value: string) => void
  onBlur: () => void
  onKeydownEnter: () => void
  forwardRef: (el: InputInstance) => void
}

const InputCell: FunctionalComponent = ({
  value,
  onChange,
  onBlur,
  onKeydownEnter,
  forwardRef,
}) => {
  return (

  )
}

const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
  Array.from({ length }).map((_, columnIndex) => ({
    ...props,
    key: `${prefix}${columnIndex}`,
    dataKey: `${prefix}${columnIndex}`,
    title: `Column ${columnIndex}`,
    width: 150,
  }))

const generateData = (
  columns: ReturnType,
  length = 200,
  prefix = 'row-'
) =>
  Array.from({ length }).map((_, rowIndex) => {
    return columns.reduce(
      (rowData, column, columnIndex) => {
        rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`
        return rowData
      },
      {
        id: `${prefix}${rowIndex}`,
        editing: false,
        parentId: null,
      }
    )
  })

const columns: Column[] = generateColumns(10)
columns[0] = {
  ...columns[0],
  title: 'Editable Column',
  cellRenderer: ({ rowData, column }) => {
    const onChange = (value: string) => {
      rowData[column.dataKey!] = value
    }
    const onEnterEditMode = () => {
      rowData.editing = true
    }

    const onExitEditMode = () => (rowData.editing = false)
    const input = ref()
    const setRef = (el) => {
      input.value = el
      if (el) {
        el.focus?.()
      }
    }

    return rowData.editing ? (

    ) : (

        {rowData[column.dataKey!]}


    )
  },
}

const data = ref(generateData(columns, 200))
.table-v2-inline-editing-trigger {
  border: 1px transparent dotted;
  padding: 4px;
}

.table-v2-inline-editing-trigger:hover {
  border-color: var(--el-color-primary);
}

상태가 있는 테이블 (Table With Status)

테이블 콘텐츠를 강조해서 "success, information, warning, danger" 등의 상태를 구분할 수 있어요. 행의 모양을 커스터마이즈하려면 row-class-name 속성을 사용해요. 예를 들어 10번째마다 행은 bg-blue-200 클래스로, 5번째마다 행은 bg-red-100 클래스로 강조돼요.

고정 행이 있는 테이블 (Table With Sticky Rows)

일부 행을 테이블 상단에 고정할 수 있는데, fixed-data 속성만 사용하면 아주 쉽게 구현할 수 있어요. 아래 예시처럼 스크롤 이벤트를 기반으로 고정 행을 동적으로 설정할 수도 있어요.

고정 열이 있는 테이블 (Table With Fixed Columns)

어떤 이유로 열을 왼쪽이나 오른쪽에 고정하고 싶다면 테이블에 특별한 속성을 추가해서 구현할 수 있어요. 열의 fixed 속성을 true(고정 방향 FixedDir.LEFT를 의미) 또는 FixedDir.LEFT, FixedDir.RIGHT로 설정할 수 있어요.

그룹 헤더 (Grouping Header)

헤더 렌더러를 커스터마이즈하면 아래 예시처럼 헤더를 그룹화할 수 있어요.

TIP 이 예시에서는 플레이그라운드에서 지원하지 않는 JSX 기능을 사용했어요. 로컬 환경이나 codesandbox 같은 온라인 IDE에서 시도해볼 수 있어요. VNode 조작이 포함되어 있으므로 테이블 컴포넌트는 JSX로 작성하는 것을 권장해요.

.el-el-table-v2__header-row .custom-header-cell {
  border-right: 1px solid var(--el-border-color);
}

.el-el-table-v2__header-row .custom-header-cell:last-child {
  border-right: none;
}

.el-primary-color {
  background-color: var(--el-color-primary);
  color: var(--el-color-white);
  font-size: 14px;
  font-weight: bold;
}

.el-primary-color .custom-header-cell {
  padding: 0 4px;
}

필터 (Filter)

Virtualized Table은 커스텀 헤더를 만들기 위한 커스텀 헤더 렌더러를 제공해요. 이를 활용해서 필터를 렌더링할 수 있어요.

.el-table-v2__demo-filter {
  border-top: var(--el-border);
  margin: 12px -12px -12px;
  padding: 0 12px;
  display: flex;
  justify-content: space-between;
}
.el-table-v2__demo-filter-btn {
  display: flex;
  cursor: pointer;
  padding: 0;
  margin: 0;
  background-color: transparent;
  appearance: none;
  border: none;
}

정렬 가능 (Sortable)

정렬 상태로 테이블을 정렬할 수 있어요.

제어 정렬 (Controlled Sort)

필요에 따라 여러 정렬 가능한 열을 정의할 수 있어요. 정렬 가능한 열이 여러 개면 어떤 열이 현재 정렬 중인지 명확하지 않아 사용자에게 UI가 혼란스러워 보일 수 있다는 점에 주의하세요.

교차 호버링 (Cross Hovering)

큰 리스트를 다룰 때 현재 방문 중인 행과 열을 놓치기 쉬워요. 이런 경우 이 기능이 매우 유용할 수 있어요.

import { ref } from 'vue'

const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
  Array.from({ length }).map((_, columnIndex) => ({
    ...props,
    key: `${prefix}${columnIndex}`,
    dataKey: `${prefix}${columnIndex}`,
    title: `Column ${columnIndex}`,
    width: 150,
  }))

const generateData = (
  columns: ReturnType,
  length = 200,
  prefix = 'row-'
) =>
  Array.from({ length }).map((_, rowIndex) => {
    return columns.reduce(
      (rowData, column, columnIndex) => {
        rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`
        return rowData
      },
      {
        id: `${prefix}${rowIndex}`,
        parentId: null,
      }
    )
  })

const columns = generateColumns(10)
columns.unshift({
  key: 'column-n-1',
  width: 50,
  title: 'Row No.',
  cellRenderer: ({ rowIndex }) => `${rowIndex + 1}`,
  align: 'center',
})
const data = generateData(columns, 200)

const cellProps = ({ columnIndex }) => {
  const key = `hovering-col-${columnIndex}`
  return {
    ['data-key']: key,
    onMouseenter: () => {
      kls.value = key
    },
    onMouseleave: () => {
      kls.value = ''
    },
  }
}

const kls = ref('')
.hovering-col-0 [data-key='hovering-col-0'],
.hovering-col-1 [data-key='hovering-col-1'],
.hovering-col-2 [data-key='hovering-col-2'],
.hovering-col-3 [data-key='hovering-col-3'],
.hovering-col-4 [data-key='hovering-col-4'],
.hovering-col-5 [data-key='hovering-col-5'],
.hovering-col-6 [data-key='hovering-col-6'],
.hovering-col-7 [data-key='hovering-col-7'],
.hovering-col-8 [data-key='hovering-col-8'],
.hovering-col-9 [data-key='hovering-col-9'],
.hovering-col-10 [data-key='hovering-col-10'] {
  background: var(--el-table-row-hover-bg-color);
}

[data-key='hovering-col-0'] {
  font-weight: bold;
  user-select: none;
  pointer-events: none;
}

Colspan

가상화 테이블은 내장된 table 요소를 사용하지 않으므로 colspan과 rowspan이 TableV1과 조금 다르게 동작해요. 하지만 커스텀 행 렌더러를 사용하면 이러한 기능들을 여전히 구현할 수 있어요. 이 섹션에서 그 방법을 보여드릴게요.

Rowspan

Colspan을 다뤘으니, row span도 있다는 점을 언급할게요. colspan과는 조금 다르지만 기본 컨셉은 동일해요.

Rowspan과 Colspan 함께 사용하기 (Rowspan and Colspan Together)

rowspan과 colspan을 함께 조합해서 비즈니스 목표를 달성할 수 있어요!

트리 데이터 (Tree Data)

가상 테이블도 트리 구조 형태로 데이터를 렌더링할 수 있어요. 화살표 아이콘을 클릭하면 트리 노드를 펼치거나 접을 수 있어요.

동적 높이 행 (Dynamic Height Rows)

가상 테이블은 동적 높이를 가진 행을 렌더링할 수 있어요. 데이터를 다루는데 콘텐츠의 크기가 확실하지 않다면, 콘텐츠 높이에 맞춰 조절되는 행을 렌더링하는 데 이 기능이 이상적이에요. 활성화하려면 estimated-row-height 속성을 전달하세요. 추정 높이가 실제 콘텐츠와 가까울수록 렌더링 경험이 더 부드러워져요.

TIP 각 행의 높이는 행을 렌더링하는 동안 동적으로 측정돼요. 따라서 많은 양의 데이터를 표시하려고 하면 UI가 튕길 수 있어요.

상세 보기 (Detail View)

동적 높이 렌더링을 사용해서 테이블 안에 상세 보기를 표시할 수도 있어요.

결론 메시지나 정보를 표시하려고 할 때 커스텀 푸터를 렌더링할 수 있어요.

const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
  Array.from({ length }).map((_, columnIndex) => ({
    ...props,
    key: `${prefix}${columnIndex}`,
    dataKey: `${prefix}${columnIndex}`,
    title: `Column ${columnIndex}`,
    width: 150,
  }))

const generateData = (
  columns: ReturnType,
  length = 200,
  prefix = 'row-'
) =>
  Array.from({ length }).map((_, rowIndex) => {
    return columns.reduce(
      (rowData, column, columnIndex) => {
        rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`
        return rowData
      },
      {
        id: `${prefix}${rowIndex}`,
        parentId: null,
      }
    )
  })

const columns = generateColumns(10)
const data = generateData(columns, 200)

커스텀 빈 렌더러 (Customized Empty Renderer)

커스텀 빈 요소를 렌더링할 수 있어요.

const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
  Array.from({ length }).map((_, columnIndex) => ({
    ...props,
    key: `${prefix}${columnIndex}`,
    dataKey: `${prefix}${columnIndex}`,
    title: `Column ${columnIndex}`,
    width: 150,
  }))

const columns = generateColumns(10)

오버레이 (Overlay)

로딩 인디케이터 같은 것을 표시하고 싶을 때 테이블 위에 오버레이를 렌더링할 수 있어요.

import { Loading as LoadingIcon } from '@element-plus/icons-vue'

const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
  Array.from({ length }).map((_, columnIndex) => ({
    ...props,
    key: `${prefix}${columnIndex}`,
    dataKey: `${prefix}${columnIndex}`,
    title: `Column ${columnIndex}`,
    width: 150,
  }))

const generateData = (
  columns: ReturnType,
  length = 200,
  prefix = 'row-'
) =>
  Array.from({ length }).map((_, rowIndex) => {
    return columns.reduce(
      (rowData, column, columnIndex) => {
        rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`
        return rowData
      },
      {
        id: `${prefix}${rowIndex}`,
        parentId: null,
      }
    )
  })

const columns = generateColumns(10)
const data = generateData(columns, 200)
.example-showcase .el-table-v2__overlay {
  z-index: 9;
}

수동 스크롤 (Manual Scrolling)

Table V2가 제공하는 메서드를 사용해서 원하는 오프셋/행으로 수동/프로그래매틱하게 스크롤할 수 있어요.

TIP scrollToRow의 두 번째 파라미터는 스크롤 전략이며 기본값은 auto예요. auto는 스크롤할 위치를 스스로 계산해요. 특정 위치로 스크롤하고 싶다면 직접 전략을 정의할 수 있어요. 사용 가능한 옵션은 "auto" | "center" | "end" | "start" | "smart"예요. smart와 auto의 차이는 auto가 smart 스크롤 전략의 하위 집합이라는 점이에요.

import { ref } from 'vue'

import type { TableV2Instance } from 'element-plus'

const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
  Array.from({ length }).map((_, columnIndex) => ({
    ...props,
    key: `${prefix}${columnIndex}`,
    dataKey: `${prefix}${columnIndex}`,
    title: `Column ${columnIndex}`,
    width: 150,
  }))

const generateData = (
  columns: ReturnType,
  length = 200,
  prefix = 'row-'
) =>
  Array.from({ length }).map((_, rowIndex) => {
    return columns.reduce(
      (rowData, column, columnIndex) => {
        rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`
        return rowData
      },
      {
        id: `${prefix}${rowIndex}`,
        parentId: null,
      }
    )
  })

const columns = generateColumns(10)
const data = generateData(columns, 200)
const tableRef = ref()
const scrollDelta = ref(200)
const scrollRows = ref(10)

function scrollByPixels() {
  tableRef.value?.scrollToTop(scrollDelta.value)
}

function scrollByRows() {
  tableRef.value?.scrollToRow(scrollRows.value)
}

TableV2 API

TableV2 속성 (Attributes)

Name Description Type Default
cache 성능 향상을 위해 미리 렌더링하는 행의 수 number 2
estimated-row-height 동적 높이 행 렌더링을 위한 추정 행 높이 number —
header-class 헤더 래퍼에 전달되는 커스텀 클래스 이름 string / Function —
header-props 헤더 컴포넌트에 전달되는 커스텀 props 이름 object / Function —
header-cell-props 헤더 셀 컴포넌트에 전달되는 커스텀 props 이름 object / Function —
header-height 헤더의 높이는 height로 설정돼요. 배열이 주어지면 그 길이만큼 헤더 행을 렌더링해요 number/ number[] 50
footer-height 푸터 요소의 높이. 제공되면 테이블 높이 계산에 포함돼요 number 0
row-class 행 래퍼에 전달되는 커스텀 클래스 이름 string / Function —
row-key 각 행의 키. 제공하지 않으면 행의 인덱스가 돼요 string / Symbol / number id
row-props 행 컴포넌트에 전달되는 커스텀 props 이름 object / Function —
row-height 각 행의 높이. 테이블 전체 높이 계산에 사용돼요 number 50
row-event-handlers 각 행에 연결되는 핸들러 모음 object —
cell-props 각 셀(헤더 셀 제외)에 전달되는 추가 props object / Function —
columns 열 정의의 배열 Column[] —
data 테이블에서 렌더링할 데이터의 배열 Data[] []
data-getter 데이터 소스에서 데이터를 가져오는 방식을 커스터마이즈하는 메서드 Function<DataGetter> —
fixed-data 메인 콘텐츠 위, 헤더 아래에 렌더링되는 행의 데이터 object —
expand-column-key 어떤 행이 확장 가능한지 나타내는 열 키 string —
expanded-row-keys 확장된 행의 키 배열. v-model과 함께 사용할 수 있어요 KeyType[] —
default-expanded-row-keys 기본 확장 행의 키 배열. 반응형이 아니예요 KeyType[] —
class 가상 테이블의 클래스 이름. 세 개의 테이블(왼쪽, 오른쪽, 메인) 모두에 적용돼요 string / array / object —
fixed 테이블 열의 너비를 고정(fixed)할지 유연(flexible)하게 할지를 나타내는 플래그 boolean false
width required 테이블의 너비 number —
height required 테이블의 높이 number —
max-height 테이블의 최대 높이 number —
indent-size 트리 테이블의 가로 들여쓰기 number 12
h-scrollbar-size 테이블의 가로 스크롤바 크기. 가로·세로 스크롤바가 겹치는 것을 방지하는 데 사용돼요 number 6
v-scrollbar-size 테이블의 세로 스크롤바 크기. 가로·세로 스크롤바가 겹치는 것을 방지하는 데 사용돼요 number 6
scrollbar-always-on true면 마우스를 테이블 위에 올렸을 때만이 아니라 항상 스크롤바를 표시해요 boolean false
sort-by 정렬 표시기 object {}
sort-state 다중 정렬 표시기 object undefined

TableV2 슬롯 (Slots)

Name Type
cell object
header object
header-cell object
row object
footer —
empty —
overlay —

TableV2 이벤트 (Events)

Name Description Parameters
column-sort 열이 정렬될 때 호출돼요 object
expanded-rows-change 확장된 행이 변경될 때 호출돼요 KeyType[]
end-reached 테이블의 끝에 도달할 때 호출돼요. 콜백에는 남은 거리가 포함되는데, 보통 스크롤바 높이예요 Function
scroll 스크롤 후 호출돼요 object
rows-rendered 행이 렌더링될 때 호출돼요 object
row-expand 화살표 아이콘을 클릭해서 트리 노드를 펼치거나 접을 때 호출돼요 object

TableV2 Exposes

Method Description Parameters
scrollTo 주어진 위치로 스크롤 Function
scrollToLeft 주어진 가로 위치로 스크롤 Function
scrollToTop 주어진 세로 위치로 스크롤 Function
scrollToRow 지정된 스크롤 전략으로 주어진 행으로 스크롤 Function

TIP 이것들은 JavaScript Object이므로 이 속성들에는 kebab-case를 사용할 수 없어요.

Column 속성 (Attributes)

Name Description Type Default
align 테이블 셀 콘텐츠의 정렬 Alignment left
class 열의 클래스 이름 string —
key 고유 식별자 KeyType —
dataKey 데이터의 고유 식별자 KeyType —
fixed 열의 고정 방향 boolean / FixedDir false
flexGrow CSSProperties의 flex grow. 고정 테이블이 아닐 때만 유용해요 number 0
flexShrink CSSProperties의 flex shrink. 고정 테이블이 아닐 때만 유용해요 number 1
headerClass 헤더 열 클래스 커스터마이즈에 사용 string —
hidden 열이 보이지 않는지 여부 boolean —
style 열 셀의 커스텀 스타일. 그리드 셀과 병합돼요 object —
sortable 열이 정렬 가능한지 여부 boolean —
title 헤더 셀에 렌더링되는 기본 텍스트 string —
maxWidth 열의 최대 너비 number —
minWidth 열의 최소 너비 number —
width required 열의 너비 number —
cellRenderer 커스텀 셀 렌더러 VueComponent / (props: CellRenderProps) => VNode —
headerCellRenderer 커스텀 헤더 렌더러 VueComponent / (props: HeaderRenderProps) => VNode —

Typings

type HeaderClassGetter = (param: {
  columns: Column[]
  headerIndex: number
}) => string

type HeaderPropsGetter = (param: {
  columns: Column[]
  headerIndex: number
}) => Record

type HeaderCellPropsGetter = (param: {
  columns: Column[]
  column: Column
  columnIndex: number
  headerIndex: number
  style: CSSProperties
}) => Record

type RowClassGetter = (param: {
  columns: Column[]
  rowData: any
  rowIndex: number
}) => string

type RowPropsGetter = (param: {
  columns: Column[]
  rowData: any
  rowIndex: number
}) => Record

type CellPropsGetter = (param: {
  column: Column
  columns: Column[]
  columnIndex: number
  cellData: any
  rowData: any
  rowIndex: number
}) => void

type DataGetterParams = {
  columns: Column[]
  column: Column
  columnIndex: number
} & RowCommonParams

type DataGetter = (params: DataGetterParams) => T

type CellRenderProps = {
  cellData: T
  column: Column
  columns: Column[]
  columnIndex: number
  rowData: any
  rowIndex: number
}

type HeaderRenderProps = {
  column: Column
  columns: Column[]
  columnIndex: number
  headerIndex: number
}

type ScrollParams = {
  xAxisScrollDir: 'forward' | 'backward'
  scrollLeft: number
  yAxisScrollDir: 'forward' | 'backward'
  scrollTop: number
}

type CellSlotProps = {
  column: Column
  columns: Column[]
  columnIndex: number
  depth: number
  style: CSSProperties
  rowData: any
  rowIndex: number
  isScrolling: boolean
  expandIconProps?:
    | {
        rowData: any
        rowIndex: number
        onExpand: (expand: boolean) => void
      }
    | undefined
}

type HeaderSlotProps = {
  cells: VNode[]
  columns: Column[]
  headerIndex: number
}

type HeaderCellSlotProps = {
  class: string
  columns: Column[]
  column: Column
  columnIndex: number
  headerIndex: number
  style: CSSProperties
  headerCellProps?: any
  sortBy: SortBy
  sortState?: SortState | undefined
  onColumnSorted: (e: MouseEvent) => void
}

type RowCommonParams = {
  rowData: any
  rowIndex: number
}

type RowEventHandlerParams = {
  rowKey: KeyType
  event: Event
} & RowCommonParams

type RowEventHandler = (params: RowEventHandlerParams) => void
type RowEventHandlers = {
  onClick?: RowEventHandler
  onContextmenu?: RowEventHandler
  onDblclick?: RowEventHandler
  onMouseenter?: RowEventHandler
  onMouseleave?: RowEventHandler
}

type RowsRenderedParams = {
  rowCacheStart: number
  rowCacheEnd: number
  rowVisibleStart: number
  rowVisibleEnd: number
}

type RowSlotProps = {
  columns: Column[]
  rowData: any
  columnIndex: number
  rowIndex: number
  data: any
  key: number | string
  isScrolling?: boolean
  style: CSSProperties
}

type RowExpandParams = {
  expanded: boolean
  rowKey: KeyType
} & RowCommonParams

type Data = {
  [key: KeyType]: any
  children?: Array
}

type FixedData = Data

type KeyType = string | number | symbol

type ColumnSortParam = { column: Column; key: KeyType; order: SortOrder }

enum SortOrder {
  ASC = 'asc',
  DESC = 'desc',
}

enum Alignment {
  LEFT = 'left',
  CENTER = 'center',
  RIGHT = 'right',
}

type SortBy = { key: KeyType; Order: SortOrder }
type SortState = Record

FAQs

첫 번째 열에 체크박스가 있는 리스트를 어떻게 렌더링하나요?

자신만의 셀 렌더러를 정의할 수 있으므로, Customize Cell Renderer 예시가 한 것처럼 체크박스를 직접 렌더링하고 상태도 직접 유지하면 돼요.

가상화 테이블이 TableV1보다 기능이 적은 이유는 무엇인가요?

가상화 테이블은 기능을 적게 제공하고 사용자가 필요에 따라 자신만의 기능을 구현하도록 하려고 해요. 너무 많은 기능을 통합하면 코드를 유지보수하기 어려워지고, 대부분의 사용자에게는 기본 기능으로 충분해요. 몇 가지 핵심 기능은 아직 개발되지 않았어요. 여러분의 의견을 듣고 싶어요. Discord에 참여해서 소식을 받아보세요.

더 알아보기 (Learn more)