행 번호 (Line Number)
좌측 틀고정 영역에 행 순번(1, 2, 3...)을 표시하고 가상 스크롤과 자연스럽게 연동하는 방법을 학습합니다.
import * as React from 'react';
import { AXDataGrid, AXDGColumn } from '@axboot/datagrid';
import { useContainerSize } from '../hooks/useContainerSize';
import DataGridContainer from '../components/DataGridContainer';
interface Props {}
interface IListItem {
id: string;
title: string;
writer: string;
createAt: string;
}
const list = Array.from(Array(5)).map((v, i) => ({
values: {
id: `ID_${i}`,
title: `title_${i}`,
writer: `writer_${i}`,
createAt: `2022-09-08`,
},
}));
function LineNumberExample(props: Props) {
const [columns, setColumns] = React.useState<AXDGColumn<IListItem>[]>([
{
key: 'id',
label: '아이디 IS LONG !',
width: 100,
},
{
key: 'title',
label: '제목',
width: 300,
itemRender: ({ values }) => {
return (
<>
{values.writer} / {values.title}
</>
);
},
},
{
key: 'writer',
label: '작성자',
width: 100,
itemRender: ({ values: values }) => {
return <>{values.writer} / A</>;
},
},
{
key: 'createAt',
label: '작성일A',
width: 100,
},
{
key: 'createAt',
label: '작성일B',
width: 100,
},
{
key: 'createAt',
label: '작성일C',
width: 100,
},
{
key: 'createAt',
label: '작성일D',
width: 100,
},
{
key: 'createAt',
label: '작성일E',
width: 100,
},
]);
const containerRef = React.useRef<HTMLDivElement>(null);
const { width: containerWidth, height: containerHeight } = useContainerSize(containerRef);
return (
<DataGridContainer ref={containerRef}>
<AXDataGrid<IListItem>
width={containerWidth}
height={containerHeight}
data={list}
columns={columns}
onChangeColumns={(columnIndex, { width, columns }) => {
console.log('onChangeColumnWidths', columnIndex, width, columns);
setColumns(columns);
}}
// rowChecked={{
// checkedIndexes: [],
// onChange: (ids, selectedAll) => {
// console.log('onChange rowSelection', ids, selectedAll);
// },
// }}
onClick={item => console.log(item)}
showLineNumber
/>
</DataGridContainer>
);
}
export default LineNumberExample;import * as React from 'react';
import './DataGridContainer.css';
interface DataGridContainerProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
}
/**
* Keeps a DataGrid in a measured, fixed layout box.
*
* AXDataGrid's rendered root is absolutely positioned within this relative
* container. This makes a ResizeObserver measurement authoritative when a
* surrounding flex or grid layout shrinks as well as when it expands.
*/
const DataGridContainer = React.forwardRef<HTMLDivElement, DataGridContainerProps>(
({ className, ...rest }, ref) => (
<div ref={ref} className={`data-grid-container ${className ?? ''}`.trim()} {...rest} />
),
);
DataGridContainer.displayName = 'DataGridContainer';
export default DataGridContainer;.data-grid-container {
position: relative;
width: 100%;
height: 400px;
overflow: hidden;
font-size: 13px;
}
.data-grid-container > .axdg-root {
position: absolute;
inset: 0;
}import * as React from 'react';
export function useContainerSize(ref: React.MutableRefObject<HTMLElement | null>, additionalDeps: unknown[] = []) {
const [width, setWidth] = React.useState(0);
const [height, setHeight] = React.useState(0);
const resizeObserver = React.useRef(
new ResizeObserver(entries => {
if (entries.length !== 1) {
throw new Error('Invalid Container length');
}
const [entry] = entries;
const { width, height } = entry.contentRect;
setWidth(width);
setHeight(height);
}),
);
React.useEffect(() => {
if (!ref.current) return;
const observer = resizeObserver.current;
const element = ref.current;
setWidth(element.clientWidth);
setHeight(element.clientHeight);
observer.observe(element);
return () => {
observer.unobserve(element);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [...additionalDeps, ref]);
return {
width,
height,
};
}1. 언제 사용하며 왜 필요한가요?
수백~수천 건의 데이터를 검토할 때 “몇 번째 행을 보고 있는지” 직관적으로 파악할 수 있도록 좌측에 행 번호(Line Number) 열을 두는 것은 스프레드시트의 기본입니다.
showLineNumber={true}를 설정하면 DataGrid 좌측에 행 번호 열이 자동으로 생성되며, 가상 스크롤 시에도 스크롤 위치에 맞춰 정확한 번호(1~N)가 고속으로 계산됩니다.
2. 실무 완성형 예제: 행 번호와 시작 번호 오프셋 지정
import React, { useState } from 'react';
import { AXDataGrid, type AXDGColumn, type AXDGDataItem } from '@axboot/datagrid';
interface Item {
code: string;
name: string;
category: string;
}
export default function LineNumberGrid() {
const [data] = useState<AXDGDataItem<Item>[]>(
Array.from({ length: 50 }).map((_, i) => ({
values: {
code: `ITEM-${1000 + i}`,
name: `품목_${i + 1}`,
category: i % 2 === 0 ? '기본형' : '고급형',
},
}))
);
const columns: AXDGColumn<Item>[] = [
{ key: 'code', label: '품목코드', width: 120, align: 'center' },
{ key: 'name', label: '품목명', width: 200 },
{ key: 'category', label: '카테고리', width: 140, align: 'center' },
];
return (
<div>
<AXDataGrid<Item>
width={650}
height={300}
columns={columns}
data={data}
rowKey="code"
showLineNumber={true} // 행 번호 표시
/>
</div>
);
}