reopt designreopt design
DocsExploreToolsPricingBuilder
Start
Start
Playground
Core Concepts
Core Concepts
DataGrid
DataGrid 개요
Editable Data Entry
Operations Monitoring
Large Data
Remote Protocol
Migration Playbook
App Composition Guide
Column Playbook
Build & Operate
Skills
Release Notes
Reference
Hook Reference
Type Reference
Oopt-datagrid
reopt designreopt design

A design system for the AI era

  • Docs
  • Pricing
  • Releases
  • GitHub
  • Terms of Service
  • Privacy Policy

© 2026 reopt-ai. All rights reserved.

DataGrid
  1. Docs
  2. /
  3. DataGrid
  4. /
  5. Column Playbook

DataGrid - 컬럼 설계 플레이북

컬럼 정의를 재사용 가능한 설계 단위로 관리하면서 파생 값 invalidation과 renderer 비용을 제어하는 패턴입니다.

reopt design · Updated Jun 26, 2026

시작하기핵심 개념개요편집형 데이터 입력운영 모니터링대규모 데이터원격 연동 계약마이그레이션 플레이북앱 조합 가이드컬럼 설계 플레이북Skills릴리즈 노트Hook 레퍼런스타입 레퍼런스

1. 컬럼 설계 원칙

- 표시용 변환은 `getValue`에, 데이터 반영은 `setValue`에 분리합니다.

- 재사용 가능한 커스텀 편집기는 column-level `editor.component` 로 선언합니다.

- 포맷(통화/상태 배지)은 `renderCell`로 표현 레이어를 고정합니다.

- `getValue`는 deterministic하고 가능한 한 cheap하게 유지합니다.

- 계산 비용이 큰 파생 컬럼은 `dependsOnColumnIds`로 invalidation 범위를 좁힙니다.

- mount 비용이 큰 셀은 `refreshCellRenderer`로 renderer reuse 조건을 명시합니다.

- 화면별로 공통 컬럼 묶음을 export해서 재사용 단위를 만듭니다.

2. 파생 컬럼과 invalidation

파생 컬럼은 표시 자체보다 invalidation 범위를 먼저 설계하는 편이 중요합니다. `dependsOnColumnIds`를 지정하면 편집된 필드와 실제로 연결된 컬럼만 refresh 대상으로 좁힐 수 있습니다.

tsx
import { type DataGridColumn } from "@reopt-ai/opt-datagrid";

interface InvoiceRow {
  id: number;
  customer: string;
  owner: string;
  amount: number;
  status: "paid" | "pending";
}

export const columns: DataGridColumn<InvoiceRow>[] = [
  {
    id: "customer",
    title: "Customer",
    width: 240,
    editable: true,
    getValue: (row) => row.customer,
    setValue: (row, nextValue) => ({ ...row, customer: nextValue }),
  },
  {
    id: "owner",
    title: "Owner",
    width: 180,
    editable: true,
    getValue: (row) => row.owner,
    setValue: (row, nextValue) => ({ ...row, owner: nextValue }),
  },
  {
    id: "summary",
    title: "Summary",
    width: 260,
    editable: false,
    dependsOnColumnIds: ["customer", "owner"],
    getValue: (row) => `${row.customer} · ${row.owner}`,
  },
  {
    id: "amount",
    title: "Amount",
    width: 140,
    align: "right",
    editable: false,
    getValue: (row) => row.amount.toLocaleString(),
  },
  {
    id: "status",
    title: "Status",
    width: 140,
    editable: false,
    getValue: (row) => row.status,
    renderCell: ({ value }) =>
      value === "paid" ? (
        <span className="rounded-full bg-emerald-100 px-2 py-0.5 text-xs text-emerald-700">Paid</span>
      ) : (
        <span className="rounded-full bg-amber-100 px-2 py-0.5 text-xs text-amber-700">Pending</span>
      ),
    refreshCellRenderer: (prev, next) => prev.value === next.value,
  },
];

3. renderer refresh 전략

- badge/action/summary 셀처럼 mount 비용이 있는 경우 `refreshCellRenderer`로 reuse 조건을 명시합니다.

- renderer 전체 정책은 `rendererRefreshMode`로 조정하고, column 단위 조건은 `refreshCellRenderer`에서 다룹니다.

- 값이 바뀌지 않은 sibling cell까지 다시 mount할 필요가 없을 때 특히 유용합니다.

4. 컬럼 인터랙션 설계

컬럼 리사이즈/재정렬/메뉴 액션은 `onColumnsChange`, `onColumnResize`, `onColumnMoved`, `onHeaderMenuAction`으로 분리해 저장합니다.

tsx
import * as React from "react";
import { DataGrid, type DataGridColumn } from "@reopt-ai/opt-datagrid";

interface Row {
  id: number;
  name: string;
  status: "active" | "blocked";
}

function ColumnManagedGrid() {
  const rows = React.useMemo<Row[]>(
    () => [
      { id: 1, name: "Alpha", status: "active" },
      { id: 2, name: "Beta", status: "blocked" },
    ],
    [],
  );
  const [columns, setColumns] = React.useState<readonly DataGridColumn<Row>[]>(
    [
      { id: "name", title: "Name", width: 220, editable: true, resizable: true, reorderable: true, menu: true, getValue: (row) => row.name, setValue: (row, v) => ({ ...row, name: v }) },
      { id: "status", title: "Status", width: 140, editable: false, resizable: true, reorderable: true, menu: true, getValue: (row) => row.status },
    ],
  );

  const saveWidth = React.useCallback((columnId: string, size: number) => {
    console.log("save width", columnId, size);
  }, []);
  const saveOrder = React.useCallback((from: number, to: number) => {
    console.log("save order", from, to);
  }, []);
  const handleSortAction = React.useCallback((colIndex: number, action: string) => {
    console.log("sort action", colIndex, action);
  }, []);

  return (
    <DataGrid
      rows={rows}
      columns={columns}
      onColumnsChange={setColumns}
      onColumnResize={(column, size) => saveWidth(column.id, size)}
      onColumnMoved={(from, to) => saveOrder(from, to)}
      onHeaderMenuAction={(colIndex, action) => handleSortAction(colIndex, action)}
    />
  );
}
PreviousApp Composition Guideopt-ui 레이아웃 안에 DataGrid를 어떤 경계로 넣고 언제 별도 엔진으로 분리할지 정리합니다.DataGrid
Go to App Composition Guide
NextSkillsopt-datagrid 설치, 마이그레이션, backend contract, 업그레이드, release용 action skill을 정리합니다.Build & Operate