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

[Spring, React] ํŽ˜์ด์ง€๋„ค์ด์…˜, ๊ณ ๊ฐ 10๋ช…๋งŒ ๋ณด์—ฌ์ฃผ๊ณ  ํŽ˜์ด์ง€ ๋„˜๊ธฐ๊ธฐ.

by ._.sori 2026. 2. 18.

 

 

 

๊ณ ๊ฐ ๋ช…๋‹จ ํ‘œ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.
๊ทธ๋Ÿผ ์ด์ œ 10๋ช…์˜ ์ •๋ณด๋งŒ ๋ณด์ด๋„๋ก
๋งŒ๋“ค๊ณ ์‹ถ๋‹ค.

 

 

 

 

1. ๋™์ž‘ํ™”๋ฉด

 

 

 

 

 


 

 

 

 

 

2. ํŽ˜์ด์ง€๋„ค์ด์…˜

 

๊ฒ€์ƒ‰ํ•ด์„œ ์ฐพ์•„๋ณธ ํŽ˜์ด์ง€๋„ค์ด์…˜์€, ๋ฐฉ๋Œ€ํ•œ ์ •๋ณด ์ค‘ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์„ ๋ณ„ํ•˜์—ฌ ์ผ๋ถ€๋ถ„๋งŒ ๋ณด์—ฌ์ฃผ๋Š” ๊ธฐ์ˆ ์„ ๋งํ•˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค. ์•„๋ฌด๋ž˜๋„ ๊ตฌ๊ธ€์— ๊ฒ€์ƒ‰ํ–ˆ์„ ๋•Œ ๊ตฌ๊ธ€์ด ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋ชจ๋“  ์ •๋ณด๋ฅผ ๋‹ค ๋ณด์—ฌ์ค€๋‹ค๋ฉด ์•„๋งˆ ์‚ฌ์šฉ์ž์˜ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๊ฐ๋‹นํ•˜์ง€ ๋ชปํ•  ๊ฒƒ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฌด์—‡๋ณด๋‹ค ๊ธฐ๋Šฅ์— ์˜๋ฏธ๊ฐ€ ์—†์–ด์งˆ ๋“ฏ ํ•˜๋‹ค. ๊ทธ๋ž˜์„œ ์ด ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž์˜ ๋ฉ”๋ชจ๋ฆฌ์™€ ์‹œ๊ฐ„์„ ์•„๋ผ๋Š” ๋“ฏ ํ•˜๋‹ค. 

 

์ผ๋‹จ ๋‚ด๊ฐ€ ํ•œ ํ”„๋กœ์ ํŠธ๋Š” ์ด๋ฏธ ๊ณ ๊ฐ ์ •๋ณด๊ฐ€ ๊ธฐ์ž…๋œ ์ƒํƒœ์ด๊ณ  ๊ทธ๊ฑธ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๊ฑฐ๊ธฐ๋‹ค ๊ณ ๊ฐ ๋ช…๋‹จ์ด ๊ทธ๋ฆฌ ๋ฐฉ๋Œ€ํ•œ ์–‘์ด ์•„๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๊ทธ๋ฆฌ ์–ด๋ ต์ง€ ์•Š์€ ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ๊ตฌํ˜„ํ•ด๋„ ๋˜๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์„ ํ–ˆ๋‹ค. ์•„๋ž˜ ๋งํฌ๋ฅผ ํ•˜๋‚˜ ๊ฑธ์–ด๋’€๋Š”๋ฐ ๊ต‰์žฅํžˆ ๋‹ค์–‘ํ•œ ๋””์ž์ธ์„ ์„ค๋ช…ํ•ด์ฃผ์…จ๋‹ค. ๊ฑฐ๊ธฐ์„œ ๋‚˜๋Š” [์ด์ „ - ์ดํ›„]๋กœ ํŽ˜์ด์ง€๋ฅผ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ์˜ ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณด๊ธฐ๋กœ ์ƒ๊ฐํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๊ฒ€์ƒ‰์„ ํ•ด์„œ ๊ณ ๊ฐ์„ ์ฐพ์œผ๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๊ตณ์ด ์ˆซ์ž๋กœ ํŽ˜์ด์ง€๋ฅผ ๋ˆ„๋ฅด๋Š” ํ˜•ํƒœ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

 

๊ฒฐ๋ก ์€ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๊ณ ๊ฐ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋ชจ๋‘ ๋ฐ›์•„์˜ฌ ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ํ”„๋ก ํŠธ์—์„œ 10๊ฐœ์”ฉ ์Šฌ๋ผ์ด์‹ฑํ•ด์„œ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ํ•ด๋ณผ ๊ฒƒ์ด๋‹ค.

 

 

 

- ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋””์ž์ธ

 

UI ๋””์ž์ธ ๊ฐ€์ด๋“œ : Pagination

ํŽ˜์ด์ง€๋„ค์ด์…˜, ๋ฌดํ•œ ์Šคํฌ๋กค, ๋” ๋ณด๊ธฐ ๋ฒ„ํŠผ์— ๊ด€ํ•˜์—ฌ UI cheat sheet: pagination, infinite scroll and the load more button ์ฝ˜ํ…์ธ ๊ฐ€ ํฌํ•จ๋œ UI๋ฅผ ์„ค๊ณ„ํ•˜๋Š” ๊ฒฝ์šฐ ์ฝ˜ํ…์ธ ๊ฐ€ ๋งŽ์„ ๋•Œ๋Š” ์ด ์„ธ ๊ฐ€์ง€ ํŒจํ„ด ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒ

brunch.co.kr

 

 

- ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋™์ž‘ ์›๋ฆฌ

 

ํŽ˜์ด์ง€๋„ค์ด์…˜(Pagination)

Pagination์˜ ๊ฐœ๋… Pagination์ด๋ž€, ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ€๋ถ„์ ์œผ๋กœ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ธฐ์ˆ ์„ ์˜๋ฏธํ•œ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ, ๋งŒ์•ฝ์— ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ฒŒ ๋˜๋ฉด ๋งค์šฐ ๋น„ํšจ์œจ์ ์ผ ๊ฒƒ์ด๋‹ค. ๋ฐ์ด

nx006.tistory.com

 

 

- ์˜คํ”„์…‹ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜

 

์ปค์„œ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜(Cursor-based-pagination) vs ์˜คํ”„์…‹ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€ ๋„ค์ด์…˜(offset-based-pagination

Pagination? ํ•œ์ •๋œ ๋„คํŠธ์›Œํฌ ์ž์›์„ ํšจ์œจ์ ์œผ๋กœ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด ํŠน์ •ํ•œ ์ •๋ ฌ ๊ธฐ์ค€์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„ํ• ํ•˜์—ฌ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ด๋‹ค. ์„œ๋ฒ„์˜ ์ž…์žฅ์—์„œ๋„ ํด๋ผ์ด์–ธํŠธ์˜ ์ž…์žฅ์—์„œ๋„ ํŠน์ •ํ•œ ์ •๋ ฌ ๊ธฐ์ค€์— ๋”ฐ

0soo.tistory.com

 

 

 

 


 

 

 

 

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

    // ํŽ˜์ด์ง€ state
    const [page, setPage] = useState(1);
    const pageSize = 10;
    const totalPages = Math.max(1, Math.ceil(rows.length / pageSize));
    const startIndex = (page - 1) * pageSize;
    const pagedRows = rows.slice(startIndex, startIndex + pageSize);
                <div className="table-footnote">
                    <div className="pagination">
                        <button
                            type="button"
                            disabled={page === 1}
                            onClick={() => setPage((prev) => Math.max(1, prev - 1))}
                        >
                            ‹
                        </button>

                        <div className="page-indicator">{page} / {totalPages}</div>

                        <button
                            type="button"
                            disabled={page === totalPages}
                            onClick={() => setPage((prev) => Math.min(totalPages, prev + 1))}
                        >
                            ›
                        </button>
                    </div>
                </div>
            </div>

 

 

 

 


 

 

 

 

4. ์ฝ”๋“œ

const [page, setPage] = useState(1);
page์˜ ์ดˆ๊ธฐ๊ฐ’์„ 1๋กœ ์„ค์ •ํ–ˆ๋‹ค. ์•„๋งˆ ํŽ˜์ด์ง€๊ฐ€ ๋„˜์–ด๊ฐ€๋ฉด setPage๋กœ 1์ด ์•„๋‹Œ 2, 3์œผ๋กœ ๋ณ€๊ฒฝ๋  ๊ฒƒ์ด๋‹ค.

 

const pageSize = 10;
10๊ฐœ์˜ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด pageSize๋Š” 10์œผ๋กœ ์ง€์ •ํ–ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด ์ฝ”๋“œ๋ฅผ ์ ๋Š”๋‹ค๊ณ  ํ•ด์„œ 10๊ฐœ์˜ ์ •๋ณด๊ฐ€ ๋ณด์ด๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. ์ •ํ™•ํžˆ๋Š” ๋’ค์˜ ์ฝ”๋“œ์—์„œ ์“ฐ๊ธฐ ์œ„ํ•ด ์„ค์ •ํ•œ ๋ณ€์ˆ˜์ด๋‹ค.

 

const totalPages = Math.max(1, Math.ceil(rows.length / pageSize));
์ „์ฒด ํŽ˜์ด์ง€๋ฅผ ์–ด๋””๊นŒ์ง€ํ• ๊ฑด์ง€ ์ •ํ•˜๋Š” ๋ถ€๋ถ„์ด๋‹ค. pageSize๋Š” ์œ„์—์„œ 10์œผ๋กœ ์ •ํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  rows.length๋Š” ๊ฐ€์ ธ์˜จ ์ „์ฒด ๊ณ ๊ฐ ๋ชฉ๋ก์˜ ๊ธธ์ด๊ฐ€ ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์˜ˆ๋ฅผ ๋“ค์–ด 57๊ฐœ์˜ ์ •๋ณด๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž. rows.length๊ฐ€ 57์ด๊ณ  10์œผ๋กœ ๋‚˜๋ˆ„๋ฉด 5.7์ด ๋œ๋‹ค.

Math.ceil์€ ๋ฐ˜์˜ฌ๋ฆผ์„ ๋งํ•œ๋‹ค. 5.7์„ ๋ฐ˜์˜ฌ๋ฆผํ•˜๋ฉด 6์ด ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ์ ์–ด๋ณด์ž. Math.max(1, 6)์ด ๋œ๋‹ค. ์ด ๋ง์€ ์ ์–ด๋„ totalPages๊ฐ€ 1 ๋ฐ‘์œผ๋กœ ๋–จ์–ด์ง€์ง„ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์™œ ์ด๋ ‡๊ฒŒ ์„ค์ •ํ–ˆ๋‚˜๋ฉด ํ™”๋ฉด์„ ๋ณด๋ฉด 1/4 ์ด๋Ÿฐ์‹์œผ๋กœ ์ ํ˜€์žˆ๋‹ค. ํŽ˜์ด์ง€๊ฐ€ ์ด์ „์„ ๋„˜๊ธฐ๋‹ค๊ฐ€ 1๋ณด๋‹ค ๋ฐ‘์ธ 0์œผ๋กœ ๋–จ์–ด์ง€๋ฉด ์•ˆ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐฉ์–ด ์ฐจ์›์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ–ˆ๋‹ค.

 

const startIndex = (page - 1) * pageSize;
startIndex๋Š” ๊ณ ๊ฐ ์ •๋ณด๋ฅผ ์–ด๋””์„œ ์ž๋ฅผ๊ฑด์ง€ ์ •ํ•˜๋Š” ๋ณ€์ˆ˜์ด๋‹ค. 57๊ฐœ์˜ ์ •๋ณด๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋ฉด ์ฒ˜์Œ์—” 0๋ฒˆ์งธ์—์„œ ๋ณด์—ฌ์ค˜์•ผํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‘๋ฒˆ์งธ ํŽ˜์ด์ง€์—์„  10์—์„œ ๋˜ 10๊ฐœ์˜ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ค˜์•ผํ•œ๋‹ค. ์ด๋Ÿฐ์‹์œผ๋กœ ์–ด๋–ค ์ •๋ณด๋ถ€ํ„ฐ ๋ณด์—ฌ์ค„์ง€ ์ •ํ•˜๊ธฐ ์œ„ํ•ด startIndex๋ฅผ ์„ค์ •ํ•ด์ค˜์•ผํ•œ๋‹ค.

page๋Š” 1์ด ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ์„ค์ •ํ–ˆ๋‹ค. page๊ฐ€ 1์ธ ๊ฒฝ์šฐ, startIndex๋Š” (1-1) x 10์œผ๋กœ 0์ด ๋œ๋‹ค. page๊ฐ€ 2์ธ ๊ฒฝ์šฐ, startIndex๋Š” (2-1) x 10์œผ๋กœ 10์ด ๋œ๋‹ค. page๊ฐ€ 3์ธ ๊ฒฝ์šฐ, startIndex๋Š” (3-1) x 10์œผ๋กœ 20์ด ๋œ๋‹ค.

 

const pagedRows = rows.slice(startIndex, startIndex + pageSize);
์ด์ œ ์œ„์— ์ ์€ ๋ณ€์ˆ˜๋“ค๋กœ ์Šฌ๋ผ์ด์‹ฑ์„ ํ•ด๋ณด์ž.

startIndex๊ฐ€ 0์ด๋ผ๋ฉด, rows.slice(0, 10);์ด ๋œ๋‹ค. ๊ทธ๋‹ˆ๊นŒ ์ „์ฒด ๊ณ ๊ฐ ๋ชฉ๋ก์„ ๋“ค๊ณ ์˜จ rows์—์„œ 0๋ฒˆ์งธ๋ถ€ํ„ฐ 9๋ฒˆ์งธ ์ž๋ฅด๊ฒŒ ๋œ๋‹ค. ์ด๋•Œ ์ฃผ์˜ํ•  ์ ์€ ์Šฌ๋ผ์ด์‹ฑ์€ startIndex+pageSize -1๋กœ ๋งˆ์ง€๋ง‰์œผ๋กœ ํฌํ•จํ•˜๋Š” ์ธ๋ฑ์Šค๊ฐ€ ๋œ๋‹ค. ๊ทธ๋ž˜์„œ 9๋ฒˆ์งธ๊นŒ์ง€ ๋ณด์—ฌ์ฃผ๊ฒŒ ๋œ๋‹ค.

๊ทธ๋ ‡๊ฒŒ ๋‚˜๋‰œ ๋ถ€๋ถ„์€ pagedRows๊ฐ€ ๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์Šฌ๋ผ์ด์‹ฑ์€ ์žˆ๋Š” ๋งŒํผ ์ž˜๋ผ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€๊ฐ€ 10๊ฐœ๊ฐ€ ์•ˆ๋œ๋‹ค๊ณ  ํ•ด๋„ ๋ฌธ์ œ ์—†๋‹ค.

 

<button
    type="button"
    disabled={page === 1}
    onClick={() => setPage((prev) => Math.max(1, prev - 1))}
    >
    ‹
</button>
< ๋ฒ„ํŠผ์€ ํŽ˜์ด์ง€๊ฐ€ 1์ด๋ฉด ์ด์ „์„ ๋ˆ„๋ฅผ ํ•„์š”๊ฐ€ ์—†๊ธฐ์— disabled๋กœ ๋ˆ„๋ฅผ ์ˆ˜ ์—†๊ฒŒ ์ฒ˜๋ฆฌํ–ˆ๋‹ค. setPage((prev) => Math.max(1, prev - 1)) ์ด ์ฝ”๋“œ๋Š” ์•„๋ž˜์„œ ๋” ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ฒ ๋‹ค.

 

<div className="page-indicator">{page} / {totalPages}</div>
ํ˜„์žฌ ํŽ˜์ด์ง€์™€ ์ „์ฒด ํŽ˜์ด์ง€๋ฅผ ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•ด ์ ์—ˆ๋‹ค. {ํ˜„์žฌ ํŽ˜์ด์ง€} / {์ „์ฒด ํŽ˜์ด์ง€}

 

<button
    type="button"
    disabled={page === totalPages}
    onClick={() => setPage((prev) => Math.min(totalPages, prev + 1))}
    >
    ‹
</button>
< ๋ฒ„ํŠผ์€ totalPages ๋ณด๋‹ค ๋” ๋’ค๋กœ ๊ฐˆ ์ˆ˜ ์—†๋„๋ก disabled๋กœ ๋‚˜ํƒ€๋ƒˆ๋‹ค.

 

 

 

 


 

 

 

 

5. ํ•จ์ˆ˜ ํ˜•ํƒœ๋กœ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ

setPage((prev) => Math.max(1, prev - 1))
setPage๋Š” useState์—์„œ ์„ค์ •ํ•œ ๊ฒƒ์ด๋‹ค. ์ด๋Ÿฐ์‹์œผ๋กœ ํ•จ์ˆ˜ ํ˜•ํƒœ๋กœ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒฝ์šฐ, ๋ฆฌ์•กํŠธ์—์„œ ๊ฐ€์žฅ ์ตœ์‹ ์˜ ์ด์ „ ์ƒํƒœ๊ฐ’์„ prev์— ๋„ฃ์–ด์ค€๋‹ค. ์ด๋ ‡๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ์ข‹์€ ์ ์ด ์‚ฌ์šฉ์ž๊ฐ€ ์—ฐ์†ํ•ด์„œ ๋น ๋ฅด๊ฒŒ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋Š” ๊ฒฝ์šฐ ์ฆ๊ฐ€๊ฐ€ ๊ผฌ์ด์ง€ ์•Š๊ณ  ๋ฌธ์ œ์—†์ด ์ฒ˜๋ฆฌ๋  ์ˆ˜ ์žˆ๋‹ค.

์‚ฌ์‹ค ์ด ์ฝ”๋“œ๋Š” AI๊ฐ€ ์ˆ˜์ •ํ•ด์ค€ ์ฝ”๋“œ์ด๋‹ค. ๊ทธ๋Ÿผ ์ „์— ์ง์ ‘ ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋Š” ์–ด๋–ค ์ฝ”๋“œ์˜€๋Š”์ง€ ๋น„๊ตํ•ด๋ณด๊ฒ ๋‹ค.

 

setPage(page + 1)
์ด๋ ‡๊ฒŒ ๋ฐ”๋กœ ๊ฐ’์„ ๋„ฃ๋Š” ํ˜•์‹์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค. ์ด ์ฝ”๋“œ๋Š” ์ง€๊ธˆ ๋‚ด๊ฐ€ ๋ณด๊ณ  ์žˆ๋Š” ํŽ˜์ด์ง€์—์„œ +1์„ ํ•œ ๊ฒƒ์ด๋‹ค.

ํ™•์‹คํžˆ AI๊ฐ€ ์ˆ˜์ •ํ•ด์ค€ ์ฝ”๋“œ๊ฐ€ ์•ˆ์ •์„ฑ์ด ๋†’์€ ๋“ฏ ํ•˜๋‹ค. ์ด๋ ‡๊ฒŒ ์ƒˆ๋กœ์šด ๊ฑธ ๋˜ ํ•˜๋‚˜ ๋ฐฐ์›Œ๊ฐ„๋‹ค.