참고할 글
[JS/리액트 ] 헷갈리는 인수와 인자 (tistory.com)
[CSS/리액트] 리액트로 CSS 옮기고 서브페이지 만들기 (9) (tistory.com)
[CSS/리액트] 리액트로 CSS 옮기고 서브페이지 만들기 (10) (tistory.com)
[CSS/리액트] 리액트로 CSS 옮기고 서브페이지 만들기 (11) (tistory.com)
자바스크립트 배열의 slice()와 splice() 함수 (tistory.com)
목표
1) 페이지 생성 방법 정리
2) 페이지 생성 유동적으로 하는 방법 알아보기
- 데이터 개수에 따라 페이지 수 늘렸다가 줄였다가 하는 방법
<< 페이지 생성 방법 >>
Array.from 요약
Array.from 요약
[JS/리액트] Array.from()에 대해서 (2) (tistory.com)
[JS/리액트] Array.from()에 대해서 (2) (tistory.com)
Array.from()
역할:
1) 배열로 변환시킬 때 사용
2) 배열 선언과 동시에 규칙을 적용해서 값을 자동으로 생성하고 싶을 때 = 매핑
변환 대상:
1) 배열처럼 동작하는 거나
2) 값을 하나씩 꺼내서 출력할 수 있는 객체들
- 첫 번째 인자 : 변환할 대상.Array.from(Array(10), (el, idx) => { return idx * 3});
주로 배열이나 배열처럼 생긴 것들(Array(10)
- 두 번째 인자: 콜백 함수라고 부르고 map() 함수의 콜백 함수랑 비슷하게 동작
각 배열 요소를 어떻게 변환할지 정하는 함수.(el, idx) => { return idx * 3})
const arr = Array.from(Array(10), (el, idx) => {
return idx * 3;
});
console.log(arr); // [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
- 배열을 선언: Array.from(Array(10))은 길이가 10인 빈 배열을 선언한 것.
- 배열의 값들을 매핑해서 생성:
(el, idx) => idx * 3 이 함수가 배열의 각 인덱스 값에 규칙을 적용.
이 규칙은 인덱스에 3을 곱한 값을 배열에 넣는 것.
배열의 각 요소가 자동으로 [0, 3, 6, 9...] 이렇게 만들어짐.
인덱스 0은 0 * 3 = 0,
인덱스 1은 1 * 3 = 3, 인덱스 2는 2 * 3 = 6... 이런 식으로 만들어지는 것.
Array.from(): 빈 배열을 인덱스에 3을 곱한 값들로 채움.
Array.from 의 2번째 인자 분석하기 >>>
- el: 각 요소의 값 (지금은 Array(10)이니까 빈 값),
- idx: 배열의 인덱스 번호 (0부터 시작해서 9까지 = 길이가 10인 빈배열을 선언했으니까).
- (_, i):
- 콜백 함수(Array.from 의 2번째 인자) 에서 첫 번째 매개변수인 배열 요소를 가리키는 자리.
- 요소를 사용하지 않겠다는 의미
- 콜백 함수에서 두 번째 매개변수인 i는 인덱스를 나타내.
이 인덱스를 통해 페이지 번호를 만들 수 있어.
다른 예시 >>
const arr = Array.from(Array(5), (el, idx) => idx * 10);
console.log(arr); // [0, 10, 20, 30, 40]
- 첫 번째 인자 Array(5)는 길이가 5인 빈 배열.
- 두 번째 인자 (el, idx) => idx * 10은 각 인덱스에 10을 곱한 값을 넣는 규칙.
페이지 생성 준비물
Pagination 컴포넌트
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleLeft, faAngleRight } from "@fortawesome/free-solid-svg-icons";
function Pagination() {
return (
<div className="pages">
{" "}
{/* class -> className */}
<a href="#">
<FontAwesomeIcon icon={faAngleLeft} />
</a>{" "}
{/* <i> 태그 대신 FontAwesomeIcon 사용 */}
<ol>
<li onClick={() => {}}>
<a href="#">1</a>
</li>
<li onClick={() => {}}>
<a href="#">2</a>
</li>
<li onClick={() => {}}>
<a href="#">3</a>
</li>
</ol>
<a href="#">
<FontAwesomeIcon icon={faAngleRight} />
</a>{" "}
{/* <i> 태그 대신 FontAwesomeIcon 사용 */}
</div>
);
}
export default Pagination;
단순 하드코딩 된 상태.
Item 컴포넌트
import axios from "axios";
import { useEffect, useState } from "react";
function ItemContainer() {
const [items, setItems] = useState([]);
useEffect(() => {
axios
.get("https://kku-git.github.io/nff_product/product.json")
.then((response) => {
setItems(response.data);
})
.catch(() => {
console.log("실패함");
});
}, []);
if (items.length === 0) {
return <p>LOADING...</p>;
}
return (
<div className="item-container">
{items.map((a, i) => (
<div key={i} className="item">
<div className="overlay-wrap">
<div className="overlay">
<p>{a.title}</p>
<p>{a.price}</p>
</div>
</div>
<img
src={`https://kku-git.github.io/nff_product/fingers/ring${
i + 1
}.jpg`}
alt={`ring ${i + 1}`}
/>
</div>
))}
</div>
);
}
export default ItemContainer;
데이터 갖고와서 넣는 곳
참고할 글
[ JS/리액트 ] axios 써서 데이터 넣는 방법 (tistory.com)
각 Page 컴포넌트 = FingerPage 컴포넌트
import Header from "../components/Header";
import LeftSidebar from "../components/LeftSidebar";
import RightSidebar from "../components/RightSidebar";
import SearchOverlay from "../components/SearchOverlay";
import Pagination from "../components/Pagination";
import Footer from "../components/Footer";
import { Link } from "react-router-dom";
import ItemContainer from "../components/Items";
function FingerPage(props) {
return (
<div className="wrapper">
{/* 검색창 */}
{props.Search === true ? (
<SearchOverlay setSearch={props.setSearch} />
) : null}
{/* 헤더 */}
<Header />
{/* 컨테이너 시작 */}
<div className="container">
{/* 왼쪽 aside */}
<LeftSidebar
setSearch={props.setSearch}
setIsShopHovered={props.setIsShopHovered}
isShopHovered={props.isShopHovered}
setIsBoardHovered={props.setIsBoardHovered}
isBoardHovered={props.isBoardHovered}
/>
{/* 중앙 메인 콘텐츠 */}
<main>
<div className="image-container">
<Link to="/">
<img
src="https://kku-git.github.io/nff_product/logo.svg"
alt="로고"
className="logo-image"
/>
</Link>
</div>
{/* items */}
<ItemContainer />
{/* pages */}
<Pagination />
{/* Footer */}
<Footer />
</main>
{/* 우측 aside */}
<RightSidebar />
</div>
</div>
);
}
export default FingerPage;
페이지 생성 방법 정리
- 순서
1. App. js 수정.
1) 현재 페이지 상태 정의
2) 총 페이지 수 지정
3) 페이지 변경 함수 생성
2. 각 Page 컴포넌트 수정
FingerPage 컴포넌트
NeckPage 컴포넌트
HairPage 컴포넌트
- 하위 컴포넌트에 받은 props 전달.
3. Pagination 컴포넌트 수정
4. ItemContainer 컴포넌트 수정
1) const startIndex = 현재 페이지의 시작 인덱스
2) const endIndex = 끝 인덱스
3) const currentItems = 현재 페이지에 맞는 상품들만 선택
1. App. js 수정
1) 현재 페이지 상태 정의
2) 총 페이지 수 지정
3) 페이지 변경 함수 생성
하위 컴포넌트에 전달이 쉽도록 App.js 에 정의.
App.js에서 상태를 관리하고
이를 FingerPage를 통해 하위 컴포넌트에 전달.
1) 현재 페이지 상태 정의
const [currentPage, setCurrentPage] = useState(1);
- 초기값으로 1을 설정
- setCurrentPage: 페이지를 변경할 때 호출 ( 현재 페이지 상태 업데이트 )
2) 총 페이지 수 지정
const totalPages = 3;
- 총 3페이지로 지정.
3) 페이지 변경 함수 생성
function handlePageChange(page) {
setCurrentPage(page); // 페이지를 변경할 때 상태를 업데이트
}
- 페이지 번호를 인수로 받음. 그 인수를 사용해 현재 페이지(currentPage)를 변경
- setCurrentPage를 호출하여 currentPage 상태를 업데이트
- onPageChange 와 연결
페이지의 상태(State) 공유 흐름 보기
1) App.js => 각 Page 컴포넌트 = FingerPage 컴포넌트
<Route
path="/shop/fingers"
element={
<FingerPage
Search={Search}
setSearch={setSearch}
isShopHovered={isShopHovered}
setIsShopHovered={setIsShopHovered}
isBoardHovered={isBoardHovered}
setIsBoardHovered={setIsBoardHovered}
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
}
/>
<FingerPage
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
각 페이지 컴포넌트( FingerPage 컴포넌트 ) 에다가 저 3가지 props 로 전달.
handlePageChange 함수는 onPageChange 로 이름 바꿨음.
2) 각 Page 컴포넌트 = FingerPage 컴포넌트 => Pagination과 ItemContainer에 다시 전달
{/* items */}
<ItemContainer currentPage={props.currentPage} itemsPerPage={9} />
{/* pages */}
<Pagination
currentPage={props.currentPage}
totalPages={props.totalPages}
onPageChange={props.onPageChange}
/>
App.js 에서 받은 props 를 Pagination과 ItemContainer에 다시 전달
☆★ Pagination에서 페이지를 변경하면,
= **onPageChange**는 사실 App.js 에 있는 **handlePageChange**랑 연결돼서 handlePageChange가 실행
page 인수 받는 경로
function handlePageChange(page) {
setCurrentPage(page); // 페이지를 변경할 때 상태를 업데이트
}
Pagination 에서 onPageChange가 호출되면, handlePageChange가 실행
onPageChange(i + 1) 여기서 페이지 번호(i + 1)를 onPageChange에 전달
onPageChange 는 handlePageChange 와 연결
즉, **onPageChange(i + 1)**가 실행되면, **handlePageChange(i + 1)**가 실행되는 구조.
경로 흐름:
페이지 번호를 클릭하면 **onClick={() => onPageChange(i + 1)}**가 실행됨.
**onPageChange(i + 1)**가 실행되면, **handlePageChange(i + 1)**가 호출됨.
handlePageChange 함수 안에서 **setCurrentPage(i + 1)**가 실행돼서,
현재 페이지(currentPage)가 i + 1로 변경
이전 페이지 버튼:
<a
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage > 1) {
onPageChange(currentPage - 1);
}
}}
>
<FontAwesomeIcon icon={faAngleLeft} />
</a>
이 부분에서 onClick 핸들러가 버튼 클릭 시 호출.
클릭 시, currentPage - 1을 인수로 onPageChange 함수가 호출.
onPageChange(currentPage - 1)은 현재 페이지보다 1 페이지 이전으로 변경해요.
------------------------------------------------------------------------------
페이지 번호 목록:
페이지 번호 목록을 <ol> 리스트로 렌더링.
각 페이지 번호를 클릭하면, onClick={() => onPageChange(i + 1)} 부분에서
onPageChange 함수가 호출.
i + 1은 현재 클릭된 페이지 번호를 의미.
------------------------------------------------------------------------------
다음 페이지 버튼:
<a href="#" onClick={(e) => { ... }}> 부분에서 onClick 핸들러가 버튼 클릭 시 호출.
클릭 시, currentPage + 1을 인수로 onPageChange 함수가 호출.
onPageChange(currentPage + 1)은 현재 페이지보다 1 페이지 이후로 변경
2. 각 Page 컴포넌트 수정
ex) FingerPage 컴포넌트
- 하위 컴포넌트에 받은 props 전달.
function FingerPage({ currentPage, totalPages, onPageChange }) {
return (
<div>
{/* 아이템 목록을 현재 페이지에 맞게 표시 */}
<ItemContainer currentPage={currentPage} itemsPerPage={9} />
{/* 페이지네이션 표시 */}
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={onPageChange}
/>
</div>
);
}
FingerPage는 받은 props들을 사용해서 하위 컴포넌트( ItemContainer와 Pagination )에 또 전달.
itemPerPage 의 쓰임 >>
한 페이지에 몇 개의 아이템(상품)을 보여줄지를 결정.
**itemsPerPage={9}**는 고정된 값이기 때문에 useState 없이 보냄.
3. Pagination 컴포넌트 수정
버튼을 누를 때 onPageChange를 호출하여 페이지를 변경
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleLeft, faAngleRight } from "@fortawesome/free-solid-svg-icons";
function Pagination({ currentPage, totalPages, onPageChange }) {
return (
<div className="pages">
{/* Previous Page Button */}
<a href="#" onClick={(e) => {
e.preventDefault();
if (currentPage > 1) {
onPageChange(currentPage - 1);
}
}}>
<FontAwesomeIcon icon={faAngleLeft} />
</a>
{/* Page Numbers */}
<ol>
{Array.from({ length: totalPages }, (_, i) => (
<li
key={i}
onClick={() => onPageChange(i + 1)}
style={{ cursor: "pointer", fontWeight: currentPage === i + 1 ? 'bold' : 'normal' }}
>
<a href="#">{i + 1}</a>
</li>
))}
</ol>
{/* Next Page Button */}
<a href="#" onClick={(e) => {
e.preventDefault();
if (currentPage < totalPages) {
onPageChange(currentPage + 1);
}
}}>
<FontAwesomeIcon icon={faAngleRight} />
</a>
</div>
);
}
export default Pagination;
1) 이전 페이지 버튼 (< 버튼)
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faAngleLeft, faAngleRight } from "@fortawesome/free-solid-svg-icons";
function Pagination({ currentPage, totalPages, onPageChange }) {
return (
<div className="pages">
{/* Previous Page Button */}
<a href="#" onClick={(e) => {
e.preventDefault(); // 페이지 새로고침 방지
if (currentPage > 1) { // 현재 페이지가 1보다 클 때만 작동
onPageChange(currentPage - 1); // 이전 페이지로 이동
}
}}>
<FontAwesomeIcon icon={faAngleLeft} /> {/* '<' 아이콘 */}
</a>
onPageChange(currentPage - 1):
< 버튼을 클릭하면 onPageChange 함수가 실행
e.preventDefault(); 사용 이유 :
= 페이지 이동 시키지 않고, 페이지 번호만 변경시키기 위해서
<a href="#"> 태그는 원래 클릭하면 페이지를 새로고침하거나 링크로 이동하는 기능을 갖고 있음.
버튼을 클릭할 때 페이지를 이동시키지 않고,
리액트에서 페이지 번호만 변경하고 싶기 때문에
e.preventDefault();를 사용해서 기본적인 링크 이동(새로고침)을 막고, 페이지 번호를 변경하는 것
onClick={(e) => { ... }}에서 e :
= 이벤트 객체
= 사용자가 어떤 이벤트(클릭, 키보드 입력 등)를 발생시켰을 때 전달되는 정보를 담고 있는 객체
+ 참고로 onClick 사용할 때는 중괄호 안에 함수 넣어야 함.
☆ if 문 html 안에는 조건문 못쓰는 걸로 아는데 이렇게도 쓸 수 있는 지 ?
2) 페이지 번호
{/* Page Numbers */}
<ol>
{Array.from({ length: totalPages }, (_, i) => (
<li
key={i}
onClick={() => onPageChange(i + 1)} // 페이지 번호 클릭 시 실행
style={{ cursor: "pointer", fontWeight: currentPage === i + 1 ? 'bold' : 'normal' }}
// 현재 페이지는 굵게 표시
>
<a href="#">{i + 1}</a> {/* 페이지 번호 */}
</li>
))}
</ol>
Array.from({ length: totalPages }) : 첫 번째 인자 / 변환할 대상
totalPages 만큼 , 전체 페이지 수만큼 배열을 생성
totalPages가 3이면, [1, 2, 3] 이렇게 3개의 번호 생성.
= 길이가 totalPages인 빈 배열을 만들겠다는 뜻
(_, i) : 두 번째 인자, 콜백 함수 / 각 배열 요소를 어떻게 변환할지 정하는 함수
요소 값은 필요 없고 **인덱스 값(i)**을 사용해서 페이지 번호를 만들기 때문에 _로 처리
onClick={() => onPageChange(i + 1)} :
페이지 번호 클릭 시 페이지로 이동.
**i + 1** = 페이지 번호
i가 0이면 1, i가 1이면 2로 이동
- 첫 번째 인덱스 i는 0이니까, i + 1은 1 → 1번째 페이지 번호
- 두 번째 인덱스 i는 1이니까, i + 1은 2 → 2번째 페이지 번호
- 세 번째 인덱스 i는 2이니까, i + 1은 3 → 3번째 페이지 번호
배열에 값이 없으니까 요소 대신 인덱스를 이용해서 값을 넣음
style={{ fontWeight: currentPage === i + 1 ? 'bold' : 'normal' }} :
현재 페이지와 같으면 그 페이지 번호를 굵게(bold) 표시
현재 활성화된 페이지와 같은 번호면 fontWeight: 'bold',
그렇지 않으면 **fontWeight: 'normal'**이 적용돼서 현재 페이지가 굵게 표시
li 태그에 key 써 준 이유
= 혹시 map 함수와 같은 형식이라서 그런가?
<li key={i}>
<a href="#">{i + 1}</a>
</li>
☆내가 누른 페이지가 3페이지인지 2페이지인지를 어떻게 인식하는지
사용자가 꼭 순차적으로 페이지를 클릭하지 않아도
i 값이 정확하게 해당 페이지 번호에 맞게 동작하는 이유
- 1페이지는 i = 0 → **i + 1 = 1**이 전달.
- 2페이지는 i = 1 → **i + 1 = 2**가 전달.
- 3페이지는 i = 2 → **i + 1 = 3**이 전달.
i 값은 이미 페이지마다 지정이 된 고정된 인덱스이기 때문에,
사용자가 순차적으로 클릭하지 않더라도
그 페이지 번호에 맞는 i + 1 값이 정확하게 전달됨.
예시 :
만약 사용자가 2페이지를 처음에 클릭했다면,
해당 리스트 항목의 **i = 1** 임.
**onClick={() => onPageChange(i + 1)}**로 인식해서 **onPageChange(2)**가 실행되는 것.
- 사용자가 어떤 페이지를 클릭하든,
- 그 페이지의 인덱스 i에 맞는 페이지 번호가 **onPageChange(i + 1)**로 전달
※ ※ 페이지 숫자를 클릭하는 순간
해당 페이지의 **인덱스(i)**에 +1을 더한 값이
onPageChange 함수에 전달되는 것. ※ ※
사용자가 2페이지를 클릭하면, 그 페이지는 **i = 1**에 해당해.
그래서 **onClick={() => onPageChange(i + 1)}**에서 **onPageChange(2)**가 실행.
onPageChange(2) 함수가 호출되면,
React의 상태가 2페이지로 변경되고,
그에 따라 2페이지에 해당하는 데이터를 가져와서 화면에 보여주게 됨.
즉, 페이지 숫자들은 미리 i + 1로 만들어진 값들이고,
클릭할 때마다 그에 맞는 페이지 번호가 함수에 파라미터(page)로 전달되는 것.
3) 다음 페이지 버튼 (> 버튼)
{/* Next Page Button */}
<a href="#" onClick={(e) => {
e.preventDefault(); // 페이지 새로고침 방지
if (currentPage < totalPages) { // 현재 페이지가 마지막 페이지보다 작을 때만 작동
onPageChange(currentPage + 1); // 다음 페이지로 이동
}
}}>
<FontAwesomeIcon icon={faAngleRight} /> {/* '>' 아이콘 */}
</a>
</div>
);
}
export default Pagination;
4. ItemContainer 컴포넌트 수정
1) const startIndex = 현재 페이지의 시작 인덱스
2) const endIndex = 끝 인덱스
3) const currentItems = 현재 페이지에 맞는 상품들만 선택
import axios from "axios";
import { useEffect, useState } from "react";
function ItemContainer({ currentPage, itemsPerPage }) {
const [items, setItems] = useState([]);
useEffect(() => {
axios
.get("https://kku-git.github.io/nff_product/product.json")
.then((response) => {
setItems(response.data);
})
.catch(() => {
console.log("실패함");
});
}, []);
if (items.length === 0) {
return <p>LOADING...</p>;
}
// Calculate the index range for current page
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const currentItems = items.slice(startIndex, endIndex);
return (
<div className="item-container">
{currentItems.map((a, i) => (
<div key={i} className="item">
<div className="overlay-wrap">
<div className="overlay">
<p>{a.title}</p>
<p>{a.price}</p>
</div>
</div>
<img
src={`https://kku-git.github.io/nff_product/fingers/ring${
startIndex + i + 1
}.jpg`}
alt={`ring ${startIndex + i + 1}`}
/>
</div>
))}
</div>
);
}
export default ItemContainer;
1) 데이터 불러오기
useEffect(() => {
axios
.get("https://kku-git.github.io/nff_product/product.json") // 외부 JSON 데이터를 가져옴
.then((response) => {
setItems(response.data); // 데이터를 items 상태에 저장
})
.catch(() => {
console.log("실패함"); // 오류 발생 시 콘솔에 메시지 출력
});
}, []);
axios 써서 불러오기
[ JS/리액트 ] axios 써서 데이터 넣는 방법 (tistory.com)
2) 현재 페이지에 맞는 데이터 선택
const startIndex = (currentPage - 1) * itemsPerPage; // 현재 페이지의 시작 인덱스
const endIndex = startIndex + itemsPerPage; // 끝 인덱스
const currentItems = items.slice(startIndex, endIndex); // 현재 페이지에 맞는 상품들만 선택
- startIndex: 현재 페이지에서 첫 번째로 보여줄 데이터의 위치.
- endIndex: 현재 페이지에서 마지막으로 보여줄 데이터의 다음 위치.
- slice 함수는 끝 인덱스 앞까지만 자르니까, 다음 인덱스까지 계산해줌.
1) 시작 인덱스 계산: 첫 번째 상품의 인덱스
const startIndex = (currentPage - 1) * itemsPerPage;
- 1) 현재 페이지에서 -1을 해서 몇 번째 그룹인지 계산 :
1페이지는 1 - 1 = 0이 되고, 2페이지는 2 - 1 = 1
상품1은 인덱스 0
상품2는 인덱스 1
상품3은 인덱스 2
상품4는 인덱스 3
상품5는 인덱스 4
상품(데이터) 순서와 배열의 인덱스가 다르게 시작하기 때문에
페이지 번호와 배열의 인덱스 차이를 맞추기 위해서 계산 해줌.
예시 >>>
1페이지에서:
우리가 보고 싶은 첫 번째 페이지의 첫 번째 상품은 배열의 인덱스 0에 있음.
currentPage가 1일 때, (1 - 1) = 0이 돼서,
인덱스 0부터 시작하여 인덱스 0부터 상품을 가져오는 것.
0 * 9 = 0
( 0 ~ 8 번째 상품까지 )
2페이지에서:
우리가 보고 싶은 두 번째 페이지의 첫 번째 상품은 배열의 인덱스 9에 있음.
currentPage가 2일 때, (2 - 1) = 1이 돼서,
1 * 9 = 9
1 그룹이 시작하는 인덱스 9부터 상품을 가져오는 것.
- 2) 한 페이지에 보여줄 상품 개수인 itemsPerPage(지금은 9)를 곱하기
1페이지의 경우: (1 - 1) * 9 = 0 → 0번째 상품부터 ( 0 ~ 8 번째 상품까지 )
2페이지의 경우: (2 - 1) * 9 = 9 → 9번째 상품부터
2) 끝 인덱스 계산: 다음페이지의 첫번째 상품 인덱스 = 그 페이지에서 마지막 상품의 다음 인덱스
const endIndex = startIndex + itemsPerPage;
예시 1)
2페이지일 때의 계산:
startIndex = (2 - 1) * 9 = 1 * 9 = 9
= 2페이지의 시작 인덱스는 9
endIndex = 9 + 9 = 18
startIndex = 9부터 endIndex = 18까지의 상품을 보여줌.
= 인덱스 9부터 인덱스 17까지의 상품들을 보여준다는 뜻
= 여기서 endIndex 는 18 이어야 함.
왜냐하면 다음페이지의 첫 번째 상품 인덱스여야 하기 때문
즉, startIndex부터 endIndex - 1까지의 상품들이
화면에(한 페이지에) 표시됨.
- + itemsPerPage을 더해주는 이유 : 몇 개의 상품을 보여줄지 계산
예시 2)
시작 인덱스 = 현재 페이지에서 보여줄 첫 번째 상품이 배열에서 어디에 있는 지 인덱스로 가리킴.
- 예: currentPage = 1 ( 1페이지에서 ) → startIndex = 0 (첫 번째 상품 인덱스 )
끝 인덱스 =그 첫 번째 상품부터 몇 개의 상품을 보여줄지 계산.
- 예: 한 페이지에 9개씩 보여준다면,
- endIndex = 0 + 9 = 9
- 인덱스 0부터 인덱스 8까지의 상품이 보여짐 그리고 다음 인덱스인 9가 endIndex
- 마지막에 계산된 숫자 9 = 그 페이지에서 마지막 상품의 다음 인덱스
3) 현재 페이지의 상품 선택
const currentItems = items.slice(startIndex, endIndex);
- slice(startIndex, endIndex): 전체 상품 목록에서 현재 페이지에 해당하는 부분만 잘라내는 함수.
slice() 에 대해서 >>
= 배열의 특정 범위를 잘라서 새로운 배열로 복사.
2개의 인자(파라미터) 를 받음
= 시작 인덱스와 끝 인덱스
= 시작하는 인덱스와 종료하는 인덱스
예시 1 >>
시작하는 인덱스 5
종료하는 인덱스 10 쓸 경우
= slice(5,10)
5부터 9까지 나온거 확인
※ 주의 ※첫번째 인자로 넘어오는 인덱스는
결과값에 포함이 되지만,
두 번째 인자로 넘어오는 종료 인덱스는
결과 값에 포함이 되지 않음
= 종료 인덱스는 원하는 값의 +1
예시 2 >>
상품이 20개 있다고 가정
상품 목록: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
한 페이지에 9개의 상품을 보여주기
1페이지에서는 1~9번 상품
2페이지에서는 10~18번 상품
3페이지에서는 19~20번 상품
slice(startIndex, endIndex) 을 사용할 경우
= startIndex부터 endIndex 직전까지 의 상품들을 잘라냄.
1페이지: slice(0, 9) → [1, 2, 3, 4, 5, 6, 7, 8, 9]
0번째 인덱스부터 9번째 인덱스 전까지 가져오니까 1번부터 9번 상품이 나와
.= 0 인덱스부터 8 인덱스 까지의 상품이 나오는 것
2페이지: slice(9, 18) → [10, 11, 12, 13, 14, 15, 16, 17, 18]
9번째 인덱스부터 18번째 인덱스 전까지 가져오니까 10번부터 18번 상품이 나와
3페이지: slice(18, 27) → [19, 20]
18번째 인덱스부터 27번째 인덱스 전까지 가져오니까 19번, 20번 상품이 나와.
이렇게 해서각 페이지에 맞는 상품들을 정확하게 가져옴.
- currentItems 의 역할
slice()를 통해 현재 페이지에서 보여줄 상품들을 가져오는 변수
= 잘라낸 상품 목록을 저장해서 현재 페이지에서 보여줄 상품들을 선택.
= 페이지에 맞게 잘려 있다.
3) 상품 보여주기
{currentItems.map((a, i) => (
<div key={i} className="item">
<div className="overlay-wrap">
<div className="overlay">
<p>{a.title}</p> // 상품 제목
<p>{a.price}</p> // 상품 가격
</div>
</div>
<img
src={`https://kku-git.github.io/nff_product/fingers/ring${startIndex + i + 1}.jpg`} // 이미지 경로
alt={`ring ${startIndex + i + 1}`}
/>
</div>
))}
기존 코드와 비교 = 모든 상품을 한 번에 화면에 보여주는 방식
<div className="item-container">
{items.map((a, i) => (
<div key={i} className="item">
<div className="overlay-wrap">
<div className="overlay">
<p>{a.title}</p> // 상품 제목
<p>{a.price}</p> // 상품 가격
</div>
</div>
<img
src={`https://kku-git.github.io/nff_product/fingers/ring${i + 1}.jpg`} // 이미지 경로
alt={`ring ${i + 1}`}
/>
</div>
))}
</div>
- items.map() 대신 **currentItems.map()**을 사용.
currentItems = slice()로 잘라낸, 현재 페이지에 맞는 상품들만 담긴 배열
= 페이지에 맞게 잘려 있음
- 이미지 경로는 startIndex + i + 1로 설정. startIndex 를 추가해줌.
startIndex + i + 1로 해야 하는 이유
i + 1만 사용하면 페이지와 상관없이 매번 새로운 페이지에서 1부터 다시 시작하게 됨.
1페이지에서는:
- 첫 번째 상품: i = 0, 그래서 i + 1 = 1, 즉 ring1.jpg
- 두 번째 상품: i = 1, 그래서 i + 1 = 2, 즉 ring2.jpg
이렇게 ring1.jpg부터 ring9.jpg까지 잘 나옴.
2페이지로 넘어가게 되면서 문제가 생김.
이제 2페이지에서도 i는 다시 0부터 시작.
map 함수는 현재 페이지의 아이템들에 대해서만 반복하기 때문에,
2페이지에서도 첫 번째 아이템에 대해 i = 0이 됨.
즉, 2페이지의 첫 번째 상품도 i + 1 = 1, 그러니까 ring1.jpg를 다시 불러오게 되는 것.
그래서 2페이지에서 보여줘야 할 상품들은 ring10.jpg, ring11.jpg 등이 되어야 하는데,
i + 1만 쓰면 계속 ring1.jpg, ring2.jpg... 이렇게 나오는 문제가 발생.
startIndex + i + 1인 경우
- 1페이지에서는 startIndex = 0이니까, i + 1만 해도 맞지만,
- 2페이지에서는 startIndex = 9이기 때문에,
- i가 0일 때 startIndex + i (0) + 1 = 10이 돼서 ring10.jpg가 보여짐.
map 함수가 어떻게 작동하는지
= 모든 상품이 아닌 현재 페이지에 맞는 상품들만 반복
2페이지에 있는 currentItems는 10번부터 18번 상품만 들어 있다면,
map 함수는 이 9개의 아이템만 반복
첫 번째 아이템(즉, 10번 상품)에 대해 i = 0,
두 번째 아이템(즉, 11번 상품)에 대해 i = 1,
...
마지막 아이템(즉, 18번 상품)에 대해 i = 8.
결론은 2페이지에 있는 상품들에 대해서는 다시 0부터 i 값이 시작
i는 현재 페이지의 첫 번째 상품에 대해 0부터 시작
결론 :
map 함수는 currentItems 배열에 있는 상품들만 반복
- currentItems 에는 10번부터 18번까지의 9개 상품만 들어 있음. ( 현재 페이지에 보여줄 상품들만 포함)
- map 함수는 이 9개 아이템을 하나씩 반복.
즉, currentItems에 들어 있는 9개의 상품에 대해
첫 번째 상품부터 마지막 상품까지 차례대로 처리
4) 어떻게 slice가 발동되고,
그 페이지에 맞는 상품을 보여주는지 과정 알아보기
1. 사용자가 페이지 버튼을 클릭할 때
사용자가 페이지 버튼을 누르면 그에 맞는 페이지로 이동
이때 우리는 Pagination 컴포넌트에서 어떤 페이지가 클릭되었는지를 알아내서,
클릭된 페이지에 맞는 데이터를 보여줌.
<li onClick={() => onPageChange(i + 1)}>
<a href="#">{i + 1}</a>
</li>
onClick={() => onPageChange(i + 1)}는 페이지 번호를 클릭할 때마다
클릭한 페이지 번호를 onPageChange라는 함수로 전달.
예를 들어, 2페이지를 누르면 onPageChange(2)가 실행.
2. onPageChange 함수가 실행되면
onPageChange 함수는 App.js에서 정의된 handlePageChange 함수와 연결.
이 함수가 실행되면 현재 페이지 번호가 업데이트 되는 것.
function handlePageChange(page) {
setCurrentPage(page); // 클릭한 페이지 번호로 현재 페이지 상태를 업데이트
}
즉, 2페이지를 누르면 **currentPage**가 2로 업데이트 됨.
3. ItemContainer가 다시 렌더링돼
React는 상태가 업데이트되면 해당 상태를 사용하는 컴포넌트를 다시 렌더링 함.
여기서는 currentPage가 변경되었기 때문에
ItemContainer가 새로 렌더링되면서,
새로운 데이터를 표시해야 함.
ItemContainer 컴포넌트는 currentPage와 itemsPerPage를 받아서
해당 페이지에 맞는 상품들을 잘라내서 보여줌.
<ItemContainer currentPage={currentPage} itemsPerPage={9} />
4. slice를 통해 상품을 잘라냄
이제 ItemContainer 안에서 상품 목록을 **slice**로 잘라내는 부분을 볼게.
1) startIndex와 endIndex 계산
**startIndex**는 현재 페이지에서 첫 번째로 보여줄 상품의 위치를 계산.
2페이지를 누르면 currentPage가 2니까, 아래 계산식에 의해 9가 나옴.
const startIndex = (currentPage - 1) * itemsPerPage;
// 2페이지라면 (2 - 1) * 9 = 9 (9번째 인덱스부터 시작)
그리고 **endIndex**는 지금 페이지에서 마지막으로 보여줄 상품의 다음 위치를 나타냄.
여기서는 9번째 상품부터 18번째 상품까지 보여주고,
그 다음 상품은 19번째 상품을 보여줘야 함.
const endIndex = startIndex + itemsPerPage;
// 9 + 9 = 18 (9번째 상품부터 18번째 상품까지 보여줄 거야)
2) slice로 상품 잘라내기
이제 이 계산된 startIndex와 endIndex를 **slice**에 넣어서,
현재 페이지에 맞는 상품들만 가져올 수 있음.
const currentItems = items.slice(startIndex, endIndex);
예를 들어 2페이지에서는,
상품 배열에서 9번부터 18번까지의 상품을 slice(9, 18)로 잘라냄.
이렇게 잘라낸 상품들이 바로 현재 페이지에 맞는 상품이 된다.
5. 잘라낸 상품을 화면에 표시
마지막으로,
잘라낸 상품들을 화면에 표시해야 함.
잘라낸 상품 배열인 currentItems를 map 함수로 돌면서 각 상품을 렌더링해줌.
{currentItems.map((a, i) => (
<div key={i} className="item">
<img src={`...`} alt={`상품 이미지`} />
</div>
))}
이 부분에서 현재 페이지에 맞는 상품들만 화면에 그려지는 것.
최종 요약
- 사용자가 페이지 번호를 클릭하면, 그 번호에 맞게 onPageChange가 호출.
- onPageChange는 handlePageChange를 실행해서 현재 페이지 번호를 업데이트.
- 현재 페이지 번호가 변경되면 ItemContainer가 다시 렌더링되고, 새로운 페이지에 맞는 상품을 가져와야 함.
- slice(startIndex, endIndex)로 현재 페이지에 맞는 상품들만 잘라내서 화면에 표시.
'> 메모 > React' 카테고리의 다른 글
리액트 상단 로고, 제목 바꾸는 방법 (3) | 2024.10.12 |
---|---|
[ JS/리액트 ] 유동적 페이지 생성 방법 (0) | 2024.10.08 |
[ JS/리액트 ] axios 써서 데이터 넣는 방법 (1) | 2024.10.03 |
[JS/리액트 ] 헷갈리는 인수와 인자 (1) | 2024.10.02 |
[리액트] Link와 useNavigate 비교 (0) | 2024.09.27 |