행 번호 (Line Number)

좌측 틀고정 영역에 행 순번(1, 2, 3...)을 표시하고 가상 스크롤과 자연스럽게 연동하는 방법을 학습합니다.

#showLineNumber#frozen#indexing
검토일: 2026-08-23
GitHub
import * as React from 'react';
import { BGrid, BGridColumn, BGridDataItem } from 'beautiful-grid';
import { useContainerSize } from '../hooks/useContainerSize';
import DataGridContainer from '../components/DataGridContainer';

interface FulfillmentOrder {
  orderNo: string;
  customerName: string;
  channel: string;
  productName: string;
  quantity: number;
  amount: number;
  status: string;
  manager: string;
  orderedAt: string;
  promisedAt: string;
}

export const LINE_NUMBER_RECORD_COUNT = 2_500;

const customers = ['에이원 리테일', '한빛상사', '모노마켓', '오로라스토어', '동해유통', '새봄리빙'];
const channels = ['자사몰', '스마트스토어', '쿠팡', 'B2B'];
const products = ['프리미엄 무선 키보드', '27인치 QHD 모니터', 'USB-C 멀티 허브', '인체공학 마우스', '노트북 거치대'];
const statuses = ['출고 준비', '피킹 완료', '배송 중', '출고 보류'];
const managers = ['김서준', '이하린', '박도윤', '최지우', '정유진'];
const amountFormatter = new Intl.NumberFormat('ko-KR');

const addDays = (base: Date, days: number) => {
  const date = new Date(base);
  date.setUTCDate(date.getUTCDate() + days);
  return date.toISOString().slice(0, 10);
};

const list: BGridDataItem<FulfillmentOrder>[] = Array.from({ length: LINE_NUMBER_RECORD_COUNT }, (_, index) => {
  const quantity = (index % 12) + 1;
  const orderedDate = new Date(Date.UTC(2026, 3, 1 + (index % 120)));

  return {
    values: {
      orderNo: `ORD-2026-${String(index + 1).padStart(6, '0')}`,
      customerName: customers[index % customers.length],
      channel: channels[index % channels.length],
      productName: products[index % products.length],
      quantity,
      amount: quantity * (39_800 + (index % 7) * 7_500),
      status: statuses[index % statuses.length],
      manager: managers[index % managers.length],
      orderedAt: addDays(orderedDate, 0),
      promisedAt: addDays(orderedDate, 2 + (index % 4)),
    },
  };
});

function LineNumberExample() {
  const [columns, setColumns] = React.useState<BGridColumn<FulfillmentOrder>[]>([
    { key: 'orderNo', label: '주문번호', width: 140 },
    { key: 'customerName', label: '고객사', width: 140 },
    { key: 'channel', label: '주문채널', width: 100, align: 'center' },
    { key: 'productName', label: '상품명', width: 210 },
    { key: 'quantity', label: '수량', width: 70, align: 'right' },
    {
      key: 'amount',
      label: '주문금액',
      width: 120,
      align: 'right',
      itemRender: ({ values }) => <>{amountFormatter.format(values.amount)}원</>,
    },
    { key: 'status', label: '출고상태', width: 100, align: 'center' },
    { key: 'manager', label: '담당자', width: 90, align: 'center' },
    { key: 'orderedAt', label: '주문일', width: 110, align: 'center' },
    { key: 'promisedAt', label: '출고예정일', width: 110, align: 'center' },
  ]);

  const containerRef = React.useRef<HTMLDivElement>(null);
  const { width: containerWidth, height: containerHeight } = useContainerSize(containerRef);

  return (
    <DataGridContainer ref={containerRef}>
      <BGrid<FulfillmentOrder>
        width={containerWidth}
        height={containerHeight}
        data={list}
        columns={columns}
        rowKey='orderNo'
        onChangeColumns={(_columnIndex, { columns }) => setColumns(columns)}
        onClick={item => console.log(item)}
        cellSelectionOptions={{ enabled: true }}
        showLineNumber
      />
    </DataGridContainer>
  );
}

export default LineNumberExample;

1. 언제 사용하며 왜 필요한가요?

수백~수천 건의 데이터를 검토할 때 “몇 번째 행을 보고 있는지” 직관적으로 파악할 수 있도록 좌측에 행 번호(Line Number) 열을 두는 것은 스프레드시트의 기본입니다.

showLineNumber={true}를 설정하면 DataGrid 좌측에 행 번호 열이 자동으로 생성되며, 가상 스크롤 시에도 스크롤 위치에 맞춰 정확한 번호(1~N)가 고속으로 계산됩니다.

위 라이브 데모는 2,500건의 주문·출고 데이터를 사용합니다. 아래로 스크롤하면 3~4자리 행 번호에 맞춰 번호 열 너비가 자동으로 확보되는 것을 확인할 수 있습니다. 행 번호를 클릭하거나 드래그하면 해당 행 전체가, 정렬 기능이 없는 컬럼 헤더를 클릭하거나 드래그하면 해당 컬럼 전체가 선택됩니다. Shift는 연속 범위, Ctrl/Cmd는 다중 범위 선택에 사용할 수 있습니다.


2. 실무 완성형 예제: 대량 주문 데이터의 행 번호

import React, { useState } from 'react';
import { BGrid, type BGridColumn, type BGridDataItem } from 'beautiful-grid';

interface Order {
  orderNo: string;
  customerName: string;
  status: string;
}

export default function LineNumberGrid() {
  const [data] = useState<BGridDataItem<Order>[]>(
    Array.from({ length: 2500 }).map((_, i) => ({
      values: {
        orderNo: `ORD-2026-${String(i + 1).padStart(6, '0')}`,
        customerName: ['에이원 리테일', '한빛상사', '모노마켓'][i % 3],
        status: ['출고 준비', '피킹 완료', '배송 중'][i % 3],
      },
    }))
  );

  const columns: BGridColumn<Order>[] = [
    { key: 'orderNo', label: '주문번호', width: 140 },
    { key: 'customerName', label: '고객사', width: 160 },
    { key: 'status', label: '출고상태', width: 120, align: 'center' },
  ];

  return (
    <div>
      <BGrid<Order>
        width={650}
        height={300}
        columns={columns}
        data={data}
        rowKey="orderNo"
        showLineNumber={true} // 행 번호 표시
      />
    </div>
  );
}