์ด๋ฒ์ ์ด๋๋ฏผ ํด ๊ฐ๋ฐ์ ์งํํ๋ฉด์ ํ
์ด๋ธ ๊ตฌํ์ด ํ์ํด TanStack Table์ ์ฌ์ฉํ๊ฒ ๋์๋ค.
๋์์ธ ์์คํ
์ฉ ํ
์ด๋ธ ์ปดํฌ๋ํธ๋ก ๋ง๋ค์๋๋ฐ, ์ ๋ ฌ, ๋ฆฌ์ฌ์ด์ง์ฒ๋ผ TanStack Table์ด ์ ๊ณตํ๋ ๊ธฐ๋ณธ ๊ธฐ๋ฅ์ ๊ทธ๋๋ก ๊ฐ์ ธ๋ค ์ฐ๋ฉด ๋๋ ๋ถ๋ถ์ ํฌ๊ฒ ์ด๋ ต์ง ์์๋ค. ๋ค๋ง ๋ฌธ์ ๋ ์ธ๋ก ๋ณํฉ์ on/off ํ ์ ์๋ ์ต์
์ด์๋ค. ํค๋ ์ชฝ์๋ ์ด๋ฏธ rowSpan API๊ฐ ์กด์ฌํด์ ํค๋ ๊ทธ๋ฃนํ์ ๋น๊ต์ ์ฝ๊ฒ ์ฒ๋ฆฌ๊ฐ ๋๋๋ฐ, tbody ์์ญ์๋ ๊ฐ์ ๊ธฐ๋ฅ์ด ์์ด์ ์ง์ ๊ตฌํ์ด ํ์ํ๋ค. ๊ทธ๋์ ์ด๋ค ์ปฌ๋ผ์ด๋ ํ์ํ ๋๋ง rowSpan์ ์ผฐ๋ค๊ฐ ๋ ์ ์๋ ๋ฐฉ์์ผ๋ก ๊ตฌํ์ ํ ๋ฒ ๋ง๋ค์ด๋ณด์๋ค.
1. ๊ธฐ๋ณธ ์์ด๋์ด
์ฒ์์ ๋ด๊ฐ ์๊ฐํ ๊ตฌ์กฐ๋ ์ปฌ๋ผ ๋ฉํ์ enableRowSpan ์ต์ ์ ์ถ๊ฐํด์ ์ด ๋จ์๋ก ๋ณํฉ ์ฌ๋ถ๋ฅผ ์ ์ดํ๊ณ , ๋ณํฉ์ด ์ผ์ง ์ด์ ํ ๋ฐ์ดํฐ๋ฅผ ํ์ด๋ณด๋ฉด์ ๊ฐ์ ๊ฐ์ด ์ฐ์๋๋ ๊ตฌ๊ฐ์ ์ฐพ์ ๋ค์ ๊ตฌ๊ฐ์ ์ฒซ ํ์๋ ๋ณํฉ๋ ํ ์๋ฅผ ๋ฃ๊ณ , ๋๋จธ์ง ํ์ 0์ผ๋ก ํ์ํด ๋ ๋๋ง์์ ์ ์ธํ๋ ๋ฐฉ์์ ์ผ๋จ ์๊ฐํ์๋ค!
2. rowSpan ๊ณ์ฐ ๋ก์ง
rowSpan ๊ธฐ๋ฅ์ ๋ง๋ค๊ธฐ ์ํด ํ์ํ ๊ฑด ํ ์ด์์ ์ฐ์๋ ๊ฐ์ด ๋ช ํ ๋์ ์ด์ด์ง๋์ง ๊ณ์ฐํ๋ ๊ฒ์ด์๋ค. ์๋ฅผ ๋ค์ด category ์ด์ด ์๋์ฒ๋ผ ๋์ด ์๋ค๊ณ ํด๋ณด์.
0: ๊ณผ์ผ
1: ๊ณผ์ผ
2: ๊ณผ์ผ
3: ๊ณผ์ผ
4: ์ฑ์
5: ์ฑ์
์ฌ๊ธฐ์ ‘๊ณผ์ผ’์ ์ฐ์ํด์ 4๋ฒ ‘์ฑ์’๋ ์ฐ์ํด์ 2๋ฒ ๋์จ๋ค.
<td rowspan="4">๊ณผ์ผ</td> <!-- 0~3ํ -->
<td rowspan="2">์ฑ์</td> <!-- 4~5ํ -->
์ฆ, ์ฐ์๋ ๊ตฌ๊ฐ์ ๊ธธ์ด๋ฅผ ๊ตฌ๊ฐ์ ์์ ์ ์ ๋ณด๊ฐ ํ์ํ๊ณ , ๋๋ ๊ฐ ํ์ ๋ํด rowSpan ๊ฐ์ ๋ค์๊ณผ ๊ฐ์ด ๋ค๊ณ ์๋๋ก ํ๋ค.
[4, 0, 0, 0, 2, 0]
- ์ธ๋ฑ์ค 0: 4 → 0๋ฒ์งธ ํ์์ ์๋๋ก 4ํ์ ๋ณํฉํ๋ค๋ ์๋ฏธ
- ์ธ๋ฑ์ค 1~3: 0 → ์ด๋ฏธ ์์ ์ ์ ๋ณํฉ๋์ด ์์ผ๋๋ฅผ ๋ ๋๋งํ์ง ์์
- ์ธ๋ฑ์ค 4: 2 → 4๋ฒ์งธ ํ์์ ์๋๋ก 2ํ ๋ณํฉ
- ์ธ๋ฑ์ค 5: 0 → ๋ง์ฐฌ๊ฐ์ง๋ก ์จ๊น
- currentValue : ์ง๊ธ ๋ณด๊ณ ์๋ ๊ฐ์ด ๋ฌด์์ธ์ง (ex: '๊ณผ์ผ', '์ฑ์'..)
- spanStartIndex: ํ์ฌ ๊ฐ์ด ์์๋ ํ ์ธ๋ฑ์ค (ex: '๊ณผ์ผ'์ด 0๋ฒ์งธ์์ ์์ํ๋ฉด 0, '์ฑ์'๊ฐ 4๋ฒ์งธ์์ ์์ํ๋ฉด 4)
- rowIndex : ํ์ฌ ๋ช ๋ฒ์งธ ํ์ ์ํ ์ค์ธ์ง!
๋ ๋๋ง ์ rowSpan > 0์ธ ์ ๋ง๋ฅผ ๋ ๋๋ง ํ๊ณ 0์ด๋ฉด์์ฒด๋ฅผ ๋ ๋๋ง ํ์ง ์๋๋ค.
1) ์ด ๋จ์ ๋ฐ๋ณต
rowSpan์ enableRowSpan์ด ์ผ์ง ์ด๋ง ๊ณ์ฐํ๋ฉด ๋๋ฏ๋ก ์๋์ ๊ฐ์ด ์กฐ๊ฑด๋ฌธ์ ๊ฑธ์๋ค.
for column in columns:
if enableRowSpan์ด ์๋๋ฉด ์คํต
spans = []
currentValue = null
spanStartIndex = 0
ํ ๋ฐ๋ณต ์์
2) ์ฒซ ๋ฒ์งธ ํ์ ๋ฌด์กฐ๊ฑด ์๋ก์ด ๊ตฌ๊ฐ์ ์์
rowIndex = 0
currentValue = rows[0][columnId] // ex. '๊ณผ์ผ'
spanStartIndex = 0
3) ๋ค์ ํ๋ถํฐ๋ ๊ฐ์ด ๋ฐ๋๋์ง ํ์ธ
rowIndex 1: ๊ณผ์ผ → ๊ณผ์ผ (๋ณํ ์์)
rowIndex 2: ๊ณผ์ผ → ๊ณผ์ผ (๋ณํ ์์)
rowIndex 3: ๊ณผ์ผ → ๊ณผ์ผ (๋ณํ ์์)
rowIndex 4: ๊ณผ์ผ → ์ฑ์ (๊ฐ ๋ณ๊ฒฝ!)
๊ฐ์ด ๋ฐ๋๋ ์๊ฐ์ด ๋ฐ๋ก ์ด์ ๊ตฌ๊ฐ์ ์ข ๋ฃํด์ผ ํ๋ ์์ ์ด๋ค.
4) ์ด์ ๊ตฌ๊ฐ ์ข ๋ฃ ์ฒ๋ฆฌ
์ด์ ๊ตฌ๊ฐ์ spanStartIndex๋ถํฐ rowIndex - 1๊น์ง๋ค.
spanStartIndex = 0
rowIndex = 4
๊ตฌ๊ฐ = 0,1,2,3 → ๊ธธ์ด = 4
spans[0] = 4
spans[1] = 0
spans[2] = 0
spans[3] = 0
๊ทธ๋ฆฌ๊ณ ์๋ก์ด ๊ตฌ๊ฐ์ ๋ค์ ์์ํ๋ค.
currentValue = '์ฑ์'
spanStartIndex = 4
5) ๊ฒฐ๊ณผ
์ ๊ณผ์ ์ ๋ฐ๋ณตํ๋ฉด ํ๋์ ์ด์ ๋ํ rowSpan ๋ฐฐ์ด์ด ์์ฑ๋๋ค.
[4, 0, 0, 0, 2, 0]
์ด ๋ฐฐ์ด์ ๋ ๋๋ง์์ ๊ทธ๋๋ก ์ฌ์ฉํ๋ฉด ๋๋ค.
- ๊ฐ์ด 0 → ๋ ๋๋ง ์๋ต
- ๊ฐ์ด 1 ์ด์ →๋ ๋๋ง
์๋ ๋ชฉ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ๋ณํฉ ํ ์ด๋ธ์ด ๊ทธ๋ ค์ง๋ค!
const data: Product[] = [
{ category: "๊ณผ์ผ", subcategory: "์ฌ๊ณผ", price: 3000 },
{ category: "๊ณผ์ผ", subcategory: "์ฌ๊ณผ", price: 2500 },
{ category: "๊ณผ์ผ", subcategory: "๋ฐฐ", price: 4000 },
{ category: "๊ณผ์ผ", subcategory: "๋ฐฐ", price: 3500 },
{ category: "์ฑ์", subcategory: "๋ฐฐ์ถ", price: 5000 },
{ category: "์ฑ์", subcategory: "๋ฌด", price: 2000 },
];
const columns: ColumnDef<Product, any>[] = [
{
accessorKey: "category",
header: "์นดํ
๊ณ ๋ฆฌ",
meta: {
enableRowSpan: true,
},
},
{
accessorKey: "subcategory",
header: "์๋ธ์นดํ
๊ณ ๋ฆฌ",
meta: {
enableRowSpan: true,
},
},
{
accessorKey: "price",
header: "๊ฐ๊ฒฉ",
cell: (info) => `${info.getValue().toLocaleString()}์`,
},
];

'๐ WIL' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| Toss Frontend Accelerator 5๊ธฐ Pre-Course ํ๊ณ (1) | 2026.02.08 |
|---|---|
| ๋ฐฐํฌ ํ๋ก์ธ์ค ์๊ฒ ๊ฐ์ ํด๋ณด๊ธฐ (0) | 2025.11.23 |
| Framer Motion์ผ๋ก ๋ณธ Rotating Text ๋ ์ด์์ ์ฒ๋ฆฌ ๋ฐฉ์ (0) | 2025.11.16 |
| ๋์์ธ ํ ํฐ ์ค๋ณต ์์ ๊ธฐ (0) | 2025.11.08 |
| ์ด๋ฒ ์ฃผ์ ์๊ฒ ๋ ๊ฒ๋ค (0) | 2025.11.02 |