๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ’ป ํ”„๋กœ์ ํŠธ/๐Ÿ‘‘ VIP ์ดˆ๋Œ€์žฅ ๐Ÿ’Œ

[Spring, React] ๊ณ ๊ฐ ๋ช…๋‹จ์„ ๊ด€๋ฆฌํ•  ํ…Œ์ด๋ธ”(ํ‘œ) ๋งŒ๋“ค๊ธฐ!

by ._.sori 2026. 2. 16.

 

 

๊ณ ๊ฐ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ค„
ํ‘œ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž!

 

 

 

 

1. ํ™”๋ฉด

๊ณ ๊ฐ ์ •๋ณด๊ฐ€ ํ™”๋ฉด์— ๋ณด์ด๋„๋ก ํ•˜๋ฉฐ, ๋งจ ๋์— ์ˆ˜์ •๊ณผ ์‚ญ์ œ ๋ฒ„ํŠผ์ด ๋‚˜์˜ค๋„๋ก ๋งŒ๋“ค์–ด๋ณผ ๊ฒƒ์ด๋‹ค.

 

 

 

 


 

 

 

 

2. ์ „์ฒด ์ฝ”๋“œ

                     <table className="sheet">
                        <colgroup>
                            {columns.map((data, i) => (
                                <col key={i} style={{ width: data.width }} />
                            ))}
                        </colgroup>

                        <thead>
                        <tr>
                            {columns.map((data, i) => (
                                <th key={i}>
                                    {data.header}
                                </th>
                            ))}
                        </tr>
                        </thead>

                        <tbody>
                        {rows.length === 0 ? (
                            <tr className="empty-row">
                                <td colSpan={columns.length}>ํ‘œ์‹œํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.</td>
                            </tr>
                        ) : (
                            pagedRows.map((row, rIdx) => (
                                <tr key={row.id ?? startIndex + rIdx} data-grade={row.grade}>

                                    <td>{row.name}</td>
                                    <td>{row.grade}</td>
                                    <td>{row.phone}</td>
                                    <td>{row.code}</td>
                                    <td>{row.note}</td>

                                    <td>
                                        <div className="edit-delete">
                                            <button
                                               className="btn-edit"
                                                onClick={() => handleEdit(row)}
                                            >
                                                ์ˆ˜์ •
                                            </button>
                                            <button
                                                className="btn-delete"
                                                onClick={() => handleDelete(row.id)}
                                            >
                                                ์‚ญ์ œ
                                            </button>
                                        </div>
                                    </td>
                                </tr>
                            ))
                        )}
                        </tbody>
                    </table>

 

 

 

 

 


 

 

 

 

 

3. ๊ธฐ๋ณธ ๊ฐœ๋…

  • ํ…Œ์ด๋ธ”: ํ–‰๊ณผ ์—ด๋กœ ๊ตฌ์„ฑ๋œ ๊ตฌ์กฐํ™”๋œ ๋ฐ์ดํ„ฐ ์ง‘ํ•ฉ(ํ‘œ ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ)์ด๋‹ค.
  • <table> : ํ…Œ์ด๋ธ” ํƒœ๊ทธ
  • <colgroup> : ํ…Œ์ด๋ธ” ๋‚ด์—์„œ ์—ด๊ทธ๋ฃน์„ ์ง€์ •
  • <col> : <colgruop> ์•ˆ์—์„œ ์‚ฌ์šฉํ•˜๋ฉฐ, ํ…Œ์ด๋ธ” ์…€์˜ ์—ด(์„ธ๋กœ๋ฐฉํ–ฅ)์„ ๋‚˜ํƒ€๋ƒ„
  • <thead> : ๋จธ๋ฆฌ๊ธ€, ์ œ๋ชฉ์„ ๋‹ฌ๋ฉฐ <table>์ด ๋ถ€๋ชจ๊ณ  <tr>์ด ์ž์‹์ž„
  • <tr> : ํ…Œ์ด๋ธ” ์…€์˜ ํ–‰(๊ฐ€๋กœ๋ฐฉํ–ฅ)์„ ๋‚˜ํƒ€๋ƒ„
  • <th> : ํ–‰๊ณผ ์—ด์˜ ์ œ๋ชฉ์ž„
  • <tbody> : ์‹ค์ œ ๋ฐ์ดํ„ฐ ๋‚ด์šฉ์„ ์ฑ„์›€(๊ทธ๋ฃนํ™”)
  • <td> : ์‹ค์ œ ๋ฐ์ดํ„ฐ ๋‚ด์šฉ์„ ํ•˜๋‚˜ํ•˜๋‚˜ ์ฑ„์›€

 

 

 


 

 

 

 

4. ์ฝ”๋“œ ์„ค๋ช…

const columns = [
    { header: "์ด๋ฆ„", accessor: "name", width: 140 },
    { header: "๋“ฑ๊ธ‰", accessor: "grade", width: 100 },
    { header: "์—ฐ๋ฝ์ฒ˜", accessor: "phone", width: 160 },
    { header: "์ดˆ๋Œ€์ฝ”๋“œ", accessor: "code", width: 100 },
    { header: "๋ฉ”๋ชจ", accessor: "note", width: 250 },
    { header: "๊ด€๋ฆฌ", accessor: "actions", width: 140 },
];
                        <colgroup>
                            {columns.map((data, i) => (
                                <col key={i} style={{ width: data.width }} />
                            ))}
                        </colgroup>
colgroup์€ ํ…Œ์ด๋ธ” ๋‚ด์—์„œ ์—ด๊ทธ๋ฃน์„ ์ •์˜ํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ columns๋Š” return ์™ธ๋ถ€์— ๋”ฐ๋กœ ์ •์˜ํ•ด๋’€๋‹ค. map ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ด์„ ์ง€์ •ํ–ˆ๋Š”๋ฐ, map ํ•จ์ˆ˜๋Š” ๊ธฐ์กด ๋ฐฐ์—ด์˜ ๊ฐ ์š”์†Œ๋“ค์„ ์›ํ•˜๋Š” ํŠน์ • ๊ทœ์น™์ด๋‚˜ ๋กœ์ง์ด ์žˆ๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๋ณ€ํ™˜ํ•œ ํ›„ ๋ณ€ํ™˜๋œ ์š”์†Œ๋“ค๋กœ ๊ตฌ์„ฑ๋œ ์ƒˆ๋กœ์šด ๋ฐฐ์—ด์„ ๋งŒ๋“œ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. 

์ฝ”๋“œ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋Œ์•„๊ฐ€๋Š”์ง€ ์ž์„ธํ•˜๊ฒŒ ์‚ดํŽด๋ณด์ž. key์˜ i๊ฐ€ 0์ด ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ฒซ๋ฒˆ์งธ์ธ ์ด๋ฆ„์ด ํ•ด๋‹น๋˜๊ณ , style์—” ์ •์˜ํ•ด๋‘” ๋„ˆ๋น„ 140์ด ์˜ค๊ฒŒ๋œ๋‹ค. ์ด ๊ณผ์ •์ด ๋ฐฐ์—ด์ด ๋๋‚ ๋•Œ๊นŒ์ง€ ๋ฐ˜๋ณตํ•˜๊ฒŒ ๋œ๋‹ค.

data๋Š” columns์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋„๋ก ์ž„์‹œ์ ์œผ๋กœ ๋ถ™์ธ ์ด๋ฆ„์ด๋‹ค. columns๋Š” ์ด 6๊ฐœ๋‹ˆ๊นŒ 6๋ฒˆ์˜ ๋ฐ˜๋ณต์œผ๋กœ ์—ด์„ ๋งŒ๋“ค๊ฑฐ๊ณ , ๊ฐ ์—ด์˜ ํฌ๊ธฐ๋Š” columns์— ์ง€์ •๋œ width๋ฅผ ๋”ฐ๋ฅผ ๊ฒƒ์ด๋‹ค.

 

                        <thead>
                        <tr>
                            {columns.map((data, i) => (
                                <th key={i}>
                                    {data.header}
                                </th>
                            ))}
                        </tr>
                        </thead>
thead์˜ ๋ถ€๋ชจ๋Š” table์ด๊ณ  ์ž์‹์€ tr์ด๋‹ค. thead๋Š” ์ œ๋ชฉ์„ ๋‹ฌ ๋•Œ ์ ์–ด์ค€๋‹ค. ์™œ ์ด๋Ÿฐ๊ฑธ ๊ตณ์ด ์ ์„๊นŒ? ๋ฐ”๋กœ ํ…Œ์ด๋ธ” ๊ตฌ์กฐ๋ฅผ ์˜๋ฏธ์žˆ๊ฒŒ ๋‚˜๋ˆ„๊ธฐ ์œ„ํ•จ์ด๋‹ค. tr์€ ํ–‰(๊ฐ€๋กœ)์„ ์˜๋ฏธํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์•ˆ์˜ th๊ฐ€ ์ง์ ‘ ์ด๋ฆ„์ด ๋“ค์–ด๊ฐ€๋Š” ์—ญํ• ์„ ๊ฐ€์ง„๋‹ค.

๋™์ž‘์€ ์œ„์˜ ์ฝ”๋“œ์™€ ๋งค์šฐ ์œ ์‚ฌํ•˜๋‹ค. colums์˜ header๋ž€์— ์ ํžŒ ๋‚ด์šฉ์„ ๋“ค๊ณ ์™€์„œ th์— ๋„ฃ๋Š”๋‹ค. 

 

                        {/* tbody๋Š” ์‹ค์ œ ๋ฐ์ดํ„ฐ ๋‚ด์šฉ์„ ์ฑ„์šด๋‹ค. thead์™€ ์˜๋ฏธ๋ฅผ ๊ตฌ๋ถ„ํ•œ๋‹ค! */}
                        <tbody>
                        {/* ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง์œผ๋กœ ๊ธธ์ด๊ฐ€ 0์ผ ๋•Œ, (๋งž์œผ๋ฉด ์ด๊ฑฐ ์‹คํ–‰ํ•ด) : (์•„๋‹ˆ๋ฉด ์ด๊ฑฐ ์‹คํ–‰ํ•ด) */}
                        {rows.length === 0 ? (
                        {/* td๋Š” ์‹ค์ œ ๋ฐ์ดํ„ฐ๋‚˜ ๋‚ด์šฉ์„ ๋‹ด์Œ */}
                        {/* coldSpan์„ ์จ์„œ ์นธ์„ ๋ชจ๋‘ ํ•ฉ์นœ ๋’ค ํ…์ŠคํŠธ๋ฅผ ํ‘œ์‹œํ•จ */}
                            <tr className="empty-row">
                                <td colSpan={columns.length}>ํ‘œ์‹œํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.</td>
                            </tr>
                        ) : (
                            {/* pagedRows๋Š” 10๊ฐœ๋‹ˆ๊นŒ rIdx๋Š” 0์—์„œ 9๊ฐ€ ๋œ๋‹ค. */}
                            pagedRows.map((row, rIdx) => (

                        {/* ??๋Š” ๋„ ๋ณ‘ํ•ฉ ์—ฐ์‚ฐ์ž์ด๋‹ค.row.id๊ฐ€ null์ด๋ฉด startIndex + rIdx๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. */}
                        {/* row.id๊ฐ€ ์žˆ๋‹ค๋ฉด row.id๋ฅผ ์“ฐ๊ฒ ์ง€๋งŒ, ํ˜น์‹œ ๋ชจ๋ฅด๋‹ˆ ์•ˆ์ „์žฅ์น˜๋กœ ์„ค์ •ํ–ˆ๋‹ค. */}
                        {/* rIdx๋Š” 0์—์„œ 9, startIndex๋Š” ๋งŒ์•ฝ 1ํŽ˜์ด์ง€๋ผ๋ฉด 0์—์„œ ์‹œ์ž‘, 2ํŽ˜์ด์ง€๋ผ๋ฉด 10์—์„œ ์‹œ์ž‘ */}
                        {/* ๋‹ค์Œ์— ์„ค๋ช…ํ•˜๊ฒ ์ง€๋งŒ, ํŽ˜์ด์ง€๋„ค์ด์…˜์ด ์ ์šฉ๋˜์–ด์žˆ์–ด์„œ ๊ทธ๋ ‡๋‹ค. ํ•œ ํŽ˜์ด์ง€๋‹น 10๊ฐœ๋งŒ ๋ณด์ด๋„๋ก ์„ค์ •ํ–ˆ์Œ */}
                        
                        {/* tr์€ ๊ฐ ๊ฐ€๋กœํ–‰์„ ๋งํ•ด์„œ, key๋กœ ๊ฐ ํ–‰์„ ๊ตฌ๋ณ„ํ•จ */}
                                <tr key={row.id ?? startIndex + rIdx} data-grade={row.grade}>
                                
                                    {/* td๋Š” ์‹ค์ œ ๋ฐ์ดํ„ฐ์™€ ๋‚ด์šฉ์„ ๋‹ด์Œ */}
                                    {/* ์ง์ ‘ ํ•˜๋‚˜ํ•˜๋‚˜ ๋Œ๋ฆฌ๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝํ–ˆ์Œ, ๋ฒ„ํŠผ ๋„ฃ์–ด์•ผํ•ด์„œ */}
                                    {/* ๊ฐ ํ–‰์˜ key์— ๋งž์ถฐ ์ •๋ณด๋“ค์ด ๋‚˜์—ด๋จ */}
                                    <td>{row.name}</td>
                                    <td>{row.grade}</td>
                                    <td>{row.phone}</td>
                                    <td>{row.code}</td>
                                    <td>{row.note}</td>

                                    {/*์ˆ˜์ •, ์‚ญ์ œ ๋ฒ„ํŠผ๋„ ์˜†์— ๋‚˜์—ด๋จ*/}
                                    <td>
                                        <div className="edit-delete">
                                            <button
                                               className="btn-edit"
                                                onClick={() => handleEdit(row)}
                                            >
                                                ์ˆ˜์ •
                                            </button>
                                            <button
                                                className="btn-delete"
                                                onClick={() => handleDelete(row.id)}
                                            >
                                                ์‚ญ์ œ
                                            </button>
                                        </div>
                                    </td>
                                </tr>
                            ))
                        )}
                        </tbody>
                    </table>
                </div>