[Week2] Pagination - 1

tiaยท2022๋…„ 2์›” 20์ผ
0

ํŒŒ์ด๋„ํ”„๋กœ์ ํŠธ

๋ชฉ๋ก ๋ณด๊ธฐ
7/9

๐Ÿ—‚ Pagination

Pagination์— ๋Œ€ํ•œ ์ฝ”๋“œ ๊ตฌํ˜„์ด ๋ง‰๋ง‰ํ•ด์„œ codesandbox์—์„œ ๊ด€๋ จ๋œ ์ฝ”๋“œ๋ฅผ ์„œ์นญํ•ด๋ณด์•˜๋‹ค.
์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ƒ˜ํ”Œ ์ฝ”๋“œ๋“ค ์ค‘์—์„œ, ์ œ์ผ ๋งˆ์Œ์— ๋“œ๋Š” ์ฝ”๋“œ๋ฅผ ๋ณต์‚ฌํ•ด์„œ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ,
์ฒ˜์Œ ๋ณด๋Š” ๋ฌธ๋ฒ•์ด์—ˆ๊ธฐ์— ์–ด๋–ค ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•œ๊ฑด์ง€ ๊ทธ๋ฆฌ๊ณ  ์ฝ”๋“œ๊ฐ€ ๊ตฌํ˜„๋˜๋Š” ๊ณผ์ •์„ ํŒŒ์•…ํ•˜๋Š”๋ฐ ์‹œ๊ฐ„์ด ์ข€ ์†Œ์š”๋˜์—ˆ๋‹ค.

1. Pagination ์ปดํฌ๋„ŒํŠธ

์›๋ž˜์˜ ์ฝ”๋“œ์—์„œ๋Š” Prev, Next ๊ธ€์ž๋ฅผ ๋ˆŒ๋ฅด๋ฉด ํŽ˜์ด์ง€๊ฐ€ ์ด๋™๋˜๋Š” ๋ฐฉ์‹์ด์—ˆ๋Š”๋ฐ,
์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ ๋””์ž์ธ์— ๋งž๊ฒŒ react-icons์„ ์‚ฌ์šฉํ•ด์„œ ์ด๋ถ€๋ถ„์„ ๋จผ์ € ์ˆ˜์ •ํ•ด์ฃผ์—ˆ๋‹ค.

createPagination.js ํŒŒ์ผ์—์„œ pagination์ด๋ผ๋Š” ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค.
(์ด๋ถ€๋ถ„์€ ๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ ์„ค๋ช…)

pagination[0]์˜ ๊ฐ’์ด 1์ธ ๊ฒฝ์šฐ,
์™ผ์ชฝ ํ™”์‚ดํ‘œ(FaAngleLeft)๋ฅผ ํด๋ฆญ ๋ชปํ•˜๊ฒŒ๋” className์„ ์ด์šฉํ•ด ์„ค์ •ํ•˜๊ณ 
์˜ค๋ฅธ์ชฝ ํ™”์‚ดํ‘œ(FaAngleRifht)๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€๋ฅผ ํด๋ฆญํ•œ ๊ฒฝ์šฐ,
ํ™”์‚ดํ‘œ๋ฅผ ํด๋ฆญ๋ชปํ•˜๊ฒŒ๋” className์„ ์ด์šฉํ•ด ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

(์„ค๋ช…์— ํ•ด๋‹นํ•˜๋Š” ์ฝ”๋“œ๋งŒ ์•„๋ž˜์— ์ฒจ๋ถ€ํ•˜์˜€๊ณ , ์ „์ฒด์ฝ”๋“œ๋Š” ๋งจ ๋งˆ์ง€๋ง‰์— ์„ค๋ช…๋˜์–ด ์žˆ๋‹ค.)

const Ul = styled.ul`
  .left {
    margin-right: 5px;
    cursor: pointer;
  }

  .right {
    margin-left: 5px;
    cursor: pointer;
  }

  .disabled {
    color: #e0e0e0;
    pointer-events: none;
  }
`

<FaAngleLeft
  className={`${pagination[0] === currentPage ? 'disabled left' : 'left'}`}
  onClick={handleClick.bind(null, currentPage - 1)}
/>

<FaAngleRight
  className={`${pagination.reverse()[0] === currentPage ? 'disabled right' : 'right'}`} 
  onClick={handleClick.bind(null, currentPage + 1)}
/>

์œ„์˜ ์ฝ”๋“œ์—์„œ ๋ณด๋ฉด onClick() ์ด๋ฒคํŠธ์— handleClick.bind() ๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ๋ณผ ์ˆ˜๊ฐ€ ์žˆ๋Š”๋ฐ,
์ฒ˜์Œ๋ณด๋Š” ํ˜•ํƒœ์˜ ์ฝ”๋“œ์˜€๊ธฐ์— ๊ฒ€์ƒ‰์„ ํ•ด๋ณด์•˜๋‹ค.

2. bind() ๋ฉ”์†Œ๋“œ - ๊ธฐ์ดˆ

MDN ์‚ฌ์ดํŠธ
Function.prototype.bind()

bind() ๋ฉ”์†Œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ์ƒˆ๋กœ์šด ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
๋ฐ›๊ฒŒ๋˜๋Š” ์ฒซ ์ธ์ž์˜ value๋กœ๋Š” this ํ‚ค์›Œ๋“œ๋ฅผ ์„ค์ •ํ•˜๊ณ , ์ด์–ด์ง€๋Š” ์ธ์ž๋“ค์€ ๋ฐ”์ธ๋“œ๋œ ํ•จ์ˆ˜์˜ ์ธ์ˆ˜์— ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.

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

const project = {
  name: "donorticon",
  getName: function () {
    return this.name
  }
}

์œ„์™€ ๊ฐ™์ด project ๋ผ๋Š” ๊ฐ์ฒด๋ฅผ ์„ ์–ธํ•ด์ฃผ์—ˆ๋‹ค. project ๋‚ด๋ถ€์— ์„ ์–ธ๋œ getName ๋ฉ”์†Œ๋“œ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ํ˜ธ์ถœํ•ด ๋ณด์•˜๋‹ค.

const test1 = project.getName(); 
console.log(test1) // 'donorticon'

const test2 = project.getName;
console.log(test2()) // ''

const test3 = test2.bind(project);
console.log(test3()) // 'donorticon'

test1์˜ ๊ฒฝ์šฐ๋Š” ๋‹น์—ฐํ•œ ๊ฒฐ๊ณผ์ด๋‹ˆ ๋ณ„๋„์˜ ์„ค๋ช…์€ ์ƒ๋žตํ•œ๋‹ค.
test2์˜ ๊ฒฝ์šฐ ๋นˆ ๊ฐ’์ด ์ถœ๋ ฅ๋˜๋Š”๋ฐ, ๋นˆ ๊ฐ’์ด ์ถœ๋ ฅ๋˜๋Š” ์ด์œ ๋ฅผ ์ฐพ์•„๋ณด๋‹ˆ ์•„๋ž˜์™€ ๊ฐ™์•˜๋‹ค.

์ธ์ˆ˜๋กœ ์ „๋‹ฌ๋ฐ›์€ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ ์ž ํ• ๋•Œ, this์— window๋ฅผ ํ• ๋‹นํ•œ๋‹ค.
๋”ฐ๋ผ์„œ ์ด ๊ฒฝ์šฐ์—๋Š” this.name์ด window.name์ด ๋˜๋Š”๋ฐ,
window ๊ฐ์ฒด์—๋Š” name์ด ์—†์œผ๋ฏ€๋กœ ๋นˆ ๊ฐ’์ด ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์ด๋‹ค.
[์ฐธ๊ณ  ๋ธ”๋กœ๊ทธ: ํ•จ์ˆ˜ ๋ฐ”์ธ๋”ฉ]

test3์˜ ๊ฒฝ์šฐ bind() ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ this๊ฐ€ project๋ฅผ ๊ฐ€๋ฅดํ‚ค๋„๋ก ํ•œ ๊ฒƒ์ด๊ณ ,
์›ํ•˜๋Š” ๊ฐ’์„ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

3. bind() ๋ฉ”์†Œ๋“œ - ๋ถ€๋ถ„ ์ ์šฉ

์‹ค์ œ ๊ตฌํ˜„๋œ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด, handleClick.bind(null, currentPage - 1)์™€ ๊ฐ™์ด
ํ•จ์ˆ˜ ์ธ์ˆ˜๋ฅผ 2๊ฐœ๋ฅผ ๋„˜๊ฒจ์ฃผ๊ณ  ์žˆ๋‹ค. ์ด ๊ฒƒ์€ ๋˜ ๋ฌด์—‡์ผ๊นŒ....? ๐Ÿ˜ญ

MDN ์‚ฌ์ดํŠธ
๋ถ€๋ถ„ ์ ์šฉ ํ•จ์ˆ˜

bind()์˜ ๋‹ค์Œ์œผ๋กœ ๊ฐ„๋‹จํ•œ ์‚ฌ์šฉ๋ฒ•์€ ๋ฏธ๋ฆฌ ์ง€์ •๋œ ์ดˆ๊ธฐ ์ธ์ˆ˜๊ฐ€ ์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒ๋‹ˆ๋‹ค.
์ง€์ •๋  ์ดˆ๊ธฐ ์ธ์ˆ˜๊ฐ€ ์žˆ๋‹ค๋ฉด ์ œ๊ณต๋œ this ๊ฐ’์„ ๋”ฐ๋ฅด๊ณ , ๋ฐ”์ธ๋”ฉ ๋œ ํ•จ์ˆ˜์— ์ „๋‹ฌ๋˜์–ด ๋ฐ”์ธ๋”ฉ ๋œ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ๋งˆ๋‹ค ๋Œ€์ƒ ํ•จ์ˆ˜์˜ ์ธ์ˆ˜ ์•ž์— ์‚ฝ์ž…๋ฉ๋‹ˆ๋‹ค.

์ด..์ด๋Ÿฐ..??? ์Œ...๐Ÿคฏ๐Ÿคฏ๐Ÿคฏ๐Ÿคฏ
์ผ๋‹จ MDN ์‚ฌ์ดํŠธ์— ์ œ๊ณต๋œ ์˜ˆ์ œ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉฐ ์ฝ”๋“œ๋ฅผ ์งœ๋ณด์•˜๋‹ค.

function list() {
  return Array.prototype.slice.call(arguments);
}

const fixedValue = list.bind(null, 10);
console.log(fixedValue()) // [10]

console.log(fixedValue(100,200,300)) // [10, 100, 200, 300]

์•„ํ•˜! 60% ์ •๋„ ๊ฐ์ด ์˜จ ๊ฒƒ ๊ฐ™๋‹ค.
bind()์˜ ๋‘๋ฒˆ์งธ ์ธ์ž๋กœ ์ดˆ๊ธฐ๊ฐ’์„ ์„ค์ •ํ•ด์ฃผ๊ณ , ๊ทธ ์ดˆ๊ธฐ๊ฐ’์„ ์ €์žฅํ•ด ๋‘˜ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

๊ทธ๋Ÿฐ๋ฐ... Array.prototype.slice.call(arguments) ๋„ˆ๋Š” ๋ˆ„๊ตฌ๋‹ˆ?
(๊ฐ„๋‹จํžˆ ๊ฐœ๋…๋งŒ ์•Œ๊ณ ๊ฐ€์ž ๐Ÿง)

call()์€ ์ƒ์œ„ context๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฉ”์†Œ๋“œ์ด๊ณ ,
arguments๋Š” ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ์†์„ฑ์ด๋‹ค.
๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•˜์ž๋ฉด, ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋„˜์–ด์˜จ ๊ฐ’๋“ค์„ array๋กœ ๋ณ€ํ™˜ํ•˜๊ฒ ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.
[์ฐธ๊ณ  ๋ธ”๋กœ๊ทธ: Array.prototype.slice.call(arguments) ์— ๋Œ€ํ•˜์—ฌ]

Pagination ์ฝ”๋“œ์— ์œ„์˜ ์ด๋ก ๋“ค์„ ์ ์šฉํ•ด ๋ณด์ž๋ฉด,
ํ˜„์žฌ ๋‚ด๊ฐ€ ์žˆ๋Š” page ๊ฐ’์„ bind() ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด์—ˆ๋‹ค!
๐Ÿฅณ๐Ÿฅณ๐Ÿฅณ

์™ผ์ชฝ ํ™”์‚ดํ‘œ(FaAngleLeft)๋ฅผ ๋ˆ„๋ฅด๋ฉด currentPage๊ฐ’๋ณด๋‹ค 1์ด ์ž‘์•„์ง„ ํŽ˜์ด์ง€๋ฅผ ์ €์žฅํ•˜๊ณ ,
ํŽ˜์ด์ง€ ์ˆซ์ž๋ฅผ ๋ˆ„๋ฅด๋ฉด, ๊ทธ ํŽ˜์ด์ง€ ์ˆซ์ž๋ฅผ ์ €์žฅํ•˜๊ณ ,
์˜ค๋ฅธ์ชฝ ํ™”์‚ดํ‘œ(FaAngleRight)๋ฅผ ๋ˆ„๋ฅด๋ฉด currentPage๊ฐ’๋ณด๋‹ค 1์ด ์ปค์ง„ ํŽ˜์ด์ง€๋ฅผ ์ €์žฅํ•˜๊ฒŒ ๋œ๋‹ค.

const handleClick = (page) => {
  setCurrentPage(page);
}
  
// ์ค‘๊ฐ„ ์ฝ”๋“œ ์ƒ๋žต

<FaAngleLeft
	// ์ฝ”๋“œ ์ƒ๋žต 
  onClick={handleClick.bind(null, currentPage - 1)} />
{pagination.map((page, idx) => (
  <li
    // ์ฝ”๋“œ ์ƒ๋žต
    onClick={handleClick.bind(null, page)} />
  {page}
  </li>
))}
<FaAngleRight
	// ์ฝ”๋“œ ์ƒ๋žต
	onClick={handleClick.bind(null, currentPage + 1)} />

4. bind() ๋ฉ”์†Œ๋“œ.. ๊ผญ ํ•„์š”ํ•œ๊ฐ€?

์ด์ œ bind() ๋ฉ”์†Œ๋“œ์— ๋Œ€ํ•œ ๊ฐœ๋…์€ ์–ด๋Š์ •๋„ ์žกํžŒ ๊ฒƒ ๊ฐ™๋‹ค.
๊ทธ๋Ÿฐ๋ฐ...์ด ์‹œ์ ์—์„œ ํ•œ๊ฐ€์ง€ ์˜๋ฌธ์ด ๋“ค์—ˆ๋‹ค.
bind() ๋ฉ”์†Œ๋“œ๋Š” ๊ผญ ํ•„์š”ํ•œ ๋ฉ”์†Œ๋“œ์ธ๊ฐ€? bind() ๋ฉ”์†Œ๋“œ ์—†์ด ์ฝ”๋“œ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์ง€ ์•Š์„๊นŒ?

4-1. bind() ์ œ๊ฑฐ

์ผ๋‹จ ๊ฐ๊ฐ ์š”์†Œ๋“ค์˜ onClick() ๋ถ€๋ถ„์˜ bind() ๋ฅผ ์ œ๊ฑฐํ–ˆ๋‹ค.

onClick={handleClick(currentPage - 1)}
onClick={handleClick(page)}
onClick={handleClick(currentPage + 1)}

๊ทธ๋žฌ๋”๋‹ˆ ์•„๋ž˜์™€ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

4-2. ์—๋Ÿฌ ํ•ธ๋“ค๋ง

ํ•ด๋‹น ์—๋Ÿฌ์— ๋Œ€ํ•ด์„œ ๊ฒ€์ƒ‰์„ ํ•ด๋ณด๋‹ˆ,
onClick() ์ด๋ฒคํŠธ์—๋งŒ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ,
render ๋  ๋•Œ๋งˆ๋‹ค ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฌดํ•œ ๋ฃจํ”„๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.
[์ฐธ๊ณ  ๋ธ”๋กœ๊ทธ: [React] Error: Maximum update depth exceeded... ์˜ค๋ฅ˜ ํ•ด๊ฒฐ๋ฒ•]

๊ทธ๋ž˜์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•ด ์ฃผ์—ˆ๋‹ค.

onClick={() => handleClick(currentPage - 1)}
onClick={() => handleClick(page)}
onClick={() => handleClick(currentPage + 1)}

๋”ฐ๋‹ค๋‹จ ๐ŸŒŸ ์—๋Ÿฌ๋ฉ”์„ธ์ง€๋„ ์‚ฌ๋ผ์กŒ๊ณ , Pagination ๊ธฐ๋Šฅ๋„ ๋ฌธ์ œ ์—†์ด ์ž˜ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ๋‹ค!

5. << ๊ณผ >> ์•„์ด์ฝ˜ ์ถ”๊ฐ€

<< ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์–ด๋Š ํŽ˜์ด์ง€์— ์žˆ๋˜ 1ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๊ณ ,
>> ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์ œ์ผ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๊ฒŒ ์•„์ด์ฝ˜์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

<FaAngleDoubleLeft
  size="18"
  className={`${
  pagination[0] === currentPage ? 'disabled left' : 'left'}`}
  onClick={() => handleClick(1)}
/>

<FaAngleDoubleRight
  size="18"
  className={`${pagination[0] === currentPage ? 'disabled right' : 'right'}`}
  onClick={() => handleClick(maxPage)}
/>

๋‚˜ ์ž์‹  ์นญ์ฐฌํ•œ๋‹ค... ๐Ÿ‘๐Ÿป๐Ÿ‘๐Ÿป๐Ÿ˜๐Ÿ˜Š

6. client ์ตœ์ข… ์ „์ฒด ์ฝ”๋“œ

import {
  FaAngleLeft,
  FaAngleRight,
  FaAngleDoubleLeft,
  FaAngleDoubleRight,
} from 'react-icons/fa';
import {
  PaginationContainer,
  PaginationUl,
} from '../../styles/PaginationStyle';
import createPagination from './createPagination';

const Pagination = ({ maxPage, currentPage, setCurrentPage }) => {
  const { pagination } = createPagination({
    numberOfPage: 5,
    currentPage,
    maxPage,
  });

  const handleClick = (page) => {
    setCurrentPage(page);
  };

  return (
    <PaginationContainer>
      <PaginationUl>
        <FaAngleDoubleLeft
          size="18"
          className={`${
            pagination[0] === currentPage ? 'disabled left' : 'left'
          }`}
          onClick={() => handleClick(1)}
        />
        <FaAngleLeft
          size="18"
          className={`${
            pagination[0] === currentPage ? 'disabled left' : 'left'
          }`}
          onClick={() => handleClick(currentPage - 1)}
        />
        {pagination.map((page, idx) => (
          <li
            key={idx}
            className={`${currentPage === page && 'active'}`}
            onClick={() => handleClick(page)}
          >
            {page}
          </li>
        ))}
        <FaAngleRight
          size="18"
          className={`${
            pagination.reverse()[0] === currentPage ? 'disabled right' : 'right'
          }`}
          onClick={() => handleClick(currentPage + 1)}
        />
        <FaAngleDoubleRight
          size="18"
          className={`${
            pagination[0] === currentPage ? 'disabled right' : 'right'
          }`}
          onClick={() => handleClick(maxPage)}
        />
      </PaginationUl>
    </PaginationContainer>
  );
};

export default Pagination;

createPagination.js ํŒŒ์ผ์— ๋Œ€ํ•œ ์„ค๋ช…์€ ๋‹ค์Œ ํฌ์ŠคํŒ…์— ์ด์–ด์„œ ํ•˜๊ฒ ๋‹ค. ๐Ÿฅธ

0๊ฐœ์˜ ๋Œ“๊ธ€