중첩 필드(address.city)
field: 'address.city'로 중첩 객체를 읽고 씁니다. 표시·정렬·필터·편집·붙여넣기·실행 취소·변경 추적·내보내기가 모두 경로를 따릅니다.
실행 예제중첩 필드
address.city를 고치고 행 객체가 바뀌는 것 보기.
이 데모의 코드
import { createGrid, type Row } from '@freegridjs/core';
import { makeRows } from '../data';
import { button, separator, text, tr, type DemoContext } from '../ui';
const CITIES = ['Seoul', 'Busan', 'Incheon', 'Daegu', 'Tokyo', 'Paris'];
export default function mount(ctx: DemoContext) {
const h = (en: string, ko: string) => tr(ctx.lang, { en, ko });
// nested rows, as an API sends them; every fifth customer has no address yet
const data: Row[] = makeRows(300).map((r, i) => ({
id: r.id,
customer: { name: r.name, company: r.company, ...(i % 5 ? { address: { city: CITIES[i % CITIES.length], zip: String(10000 + i * 37).slice(0, 5) } } : {}) },
total: r.q1,
}));
const grid = createGrid(ctx.host, {
columns: [
{ field: 'id', header: 'ID', type: 'number', width: 70 },
{ field: 'customer.name', header: h('Customer', '고객'), width: 160 },
{ field: 'customer.company', header: h('Company', '회사'), width: 150 },
{ field: 'customer.address.city', header: h('City', '도시'), width: 120, editable: true, editor: { type: 'combo', options: CITIES } },
{ field: 'customer.address.zip', header: h('ZIP', '우편번호'), width: 100, editable: true },
{ field: 'total', header: h('Total', '합계'), type: 'number', format: '#,##0', width: 110 },
],
data, locale: ctx.lang, getRowId: r => String(r.id), filterRow: true,
});
grid.on('cellValueChanged', e => ctx.log(`#${e.row.id} ${e.colId}: ${String(e.oldValue ?? '')} → ${String(e.newValue)} · customer = ${JSON.stringify(e.row.customer)}`));
text(ctx, h('Edit a city or ZIP: the log shows the nested object. Rows without an address get one. Sort, filter and Ctrl+Z work as usual.',
'도시나 우편번호를 고쳐 보세요. 로그에 중첩 객체가 보입니다. 주소가 없던 행에는 주소가 생깁니다. 정렬, 필터, Ctrl+Z도 그대로 됩니다.'));
separator(ctx);
button(ctx, h('Changes', '변경 내역'), () => ctx.log(JSON.stringify(grid.getChanges().map(c => ({ id: c.row.id, colId: c.colId, old: c.oldValue, new: c.newValue })))));
button(ctx, h('Revert all', '모두 되돌리기'), () => grid.revertChanges());
return grid;
}API에서 오는 데이터는 자주 중첩되어 있습니다. 주문에 customer 객체가 있고 그 안에 address가 있는 식입니다. 값까지의 경로를 키 사이에 점을 찍어 컬럼의 field로 쓰면, 그리드가 그 위치에서 읽고 씁니다. 행을 미리 펼치거나 valueGetter를 쓸 필요가 없습니다.
const rows = [
{ id: 1, customer: { name: 'Kim', address: { city: 'Seoul', zip: '04524' } }, total: 1200 },
{ id: 2, customer: { name: 'Lee', address: { city: 'Busan' } }, total: 800 },
{ id: 3, customer: { name: 'Park' }, total: 450 }, // no address yet
];
createGrid(el, {
getRowId: row => String(row.id),
columns: [
{ field: 'customer.name', header: 'Customer' },
{ field: 'customer.address.city', header: 'City', editable: true },
{ field: 'customer.address.zip', header: 'ZIP', editable: true },
{ field: 'total', type: 'number' },
],
data: rows,
});경로를 따르는 기능
| 기능 | 중첩 필드일 때 |
|---|---|
| 표시, 정렬, 필터, 빠른 검색, 찾기 | 경로의 값을 읽음. 중간 객체가 없으면 빈 값 |
| 편집, 붙여넣기, 채우기, Delete | 중첩 객체에 씀. 중간 객체가 없으면 만들어짐(위 3행에 customer.address가 생김) |
| 실행 취소, revertChanges | 이전 값을 되돌림. revertChanges는 편집이 만든 객체도 지움 |
| 변경 추적 | getChanges()가 경로 컬럼과 이전·새 값을 돌려줌. 원본은 중첩 객체를 따로 복사해 두어 이후 편집에 바뀌지 않음 |
| applyTransaction(실시간 데이터) | { id, customer: { … } } 업데이트는 객체를 바꾸고, 중첩 컬럼이 다시 그려지며 깜빡이고 다시 정렬됨 |
| CSV, 엑셀, PDF, 인쇄, 가져오기 | 경로의 값을 내보냄. 가져온 파일은 중첩 객체를 채움 |
| 컬럼 id, 상태, 필터 | id를 주지 않으면 경로('customer.address.city')가 id |
점이 들어간 키
행에 경로 전체가 하나의 키로 있으면({ 'customer.name': 'Kim' }, 일부 내보내기 결과처럼) 그 키가 우선이며 평평한 키 하나로 읽고 씁니다. 이미 평평한 데이터도 그대로 동작합니다.
중첩 필드와 valueGetter
| field: 'a.b' | valueGetter | |
|---|---|---|
| 읽기 | 예 | 예 |
| 편집, 붙여넣기, 채우기 | 예 | 아니요 |
| 여러 필드로 계산 | 아니요 | 예 (row => row.qty * row.price) |
배열(items[0].name) | 'items.0.name'이 첫 항목을 읽음 | 예 |
TypeScript에서
행에 타입을 주면 편집기가 경로를 자동완성하고 검사합니다. ColumnDef<Order>는 customer.address.city를 받고 오타는 거부합니다. TypeScript를 보세요.
경로는 일반 객체와 배열(items.0.qty)을 따라갑니다. 끝에 있는 Date나 클래스 인스턴스는 값 자체입니다. 변경 추적과 실행 취소는 일반 객체와 배열만 따로 복사해 둡니다. 클래스 인스턴스는 그대로 공유하므로 그 안을 고친 것(클래스 객체에 field: 'owner.name')은 변경으로 잡히지 않습니다. 편집하는 값은 일반 객체로 두세요.
이 페이지에 대한 질문
address.city 같은 중첩 속성을 컬럼에 보여 주려면?
경로를 field에 쓰면 됩니다: { field: 'address.city' }. 다른 필드처럼 읽고 정렬·필터·편집하며, 편집하면 row.address.city에 다시 씁니다.
중첩 데이터에 아직 valueGetter가 필요한가요?
단순한 경로라면 필요 없습니다. valueGetter는 읽기만 해서 편집할 수 없지만, 점이 들어간 field는 편집됩니다. 여러 필드로 계산하는 값에만 valueGetter를 쓰세요.