이전에 정리한 페이지네이션 = 페이지 수 직접 지정
[ JS/리액트 ] 페이지 생성 정리 (tistory.com)
전반적인 흐름
- 상태와 데이터
- 우리가 데이터를 받아오기 전에는 총 페이지 수를 모름. 그래서 기본값으로 1로 설정.
- 데이터를 받아온 후, 그 데이터를 바탕으로 총 페이지 수를 계산해야 함.
- 부모와 자식 컴포넌트
- **App.js**는 총 페이지 수를 관리하는 부모 컴포넌트이고,
- **FingerPage**는 그 부모의 자식 컴포넌트이다.
- 그리고 **FingerItems**는 **FingerPage**의 자식.
- 부모 컴포넌트는 자식 컴포넌트의 데이터를 관리할 수 있어야 함.
- 총 페이지 수를 업데이트하는 이유
- **FingerItems**에서 데이터를 받아오면,
- 그 데이터의 수에 따라 총 페이지 수를 다시 계산해야 함.
- 이 계산된 값을 **부모 컴포넌트인 App.js**에 알려줘야 하니까,
- 부모 컴포넌트의 상태를 업데이트할 수 있는 함수가 필요.
App.js 수정
1) const totalPages = 3;으로 하드코딩한거 지우고
const [totalPages, setTotalPages] = useState(1); 으로 수정
- totalPages: 현재 총 페이지 수를 담고 있는 변수
- setTotalPages: totalPages 값을 실시간으로 업데이트해주는 함수
useState(1)의 의미:
데이터를 아직 가져오기 전에는 페이지 수를 일단 1로 설정.
나중에 데이터가 로드되고 나서야, 정확한 총 페이지 수를 계산해서 그 값을 업데이트.
초기값으로 1을 설정하는 이유는
페이지 수가 최종적으로 결정되기 전까지 임시로 기본값을 설정하는 것.
= 초기값 1: 데이터를 아직 못 받았을 때도 1페이지가 있다고 가정
데이터를 다 받으면: 그때 정확한 페이지 수(예를 들어 3페이지, 5페이지)가
업데이트돼서 제대로 보여지게 되는 것
임시값이 필요한 이유 :
우리가 서버에서 아이템 데이터를 받아오기 전에, 페이지 수가 몇인지 알 수 없음.
그렇지만 컴포넌트는 그 전에 이미 렌더링이 돼야 함.
그래서 데이터를 아직 못 가져온 상태에서도 컴포넌트가 작동하게 하려면 임시값이 필요함.
즉, 데이터가 아직 없을 때 라도 페이지를 표시할 수 있게
임시로 페이지 수를 1로 설정
그리고 나서 , 데이터를 다 가져온 후에 정확한 페이지 수로 업데이트
0이 아니라 1로 설정한 이유:
페이지 수를 0 으로 설정할 수도 있었지만,
일반적으로는 페이지는 1부터 시작하는 것이 더 직관적.
= 보통 1페이지 부터 시작하니까
참고하기
왜 컴포넌트가 데이터를 받기 전에 이미 렌더링이 돼야 하는지 >>
결론: 로딩페이지와 같은 형식이라 보면 된다.
1. React의 렌더링 방식
React에서는 컴포넌트가 처음에 렌더링될 때,
우리가 필요로 하는 모든 데이터가 이미 준비되어 있을 필요는 없어요.
React는 데이터를 비동기적으로 가져오는 경우가 많기 때문에,
데이터를 가져오는 동안에도 컴포넌트가 먼저 화면에 나타나는 것이 일반적이에요.
즉, 컴포넌트는 먼저 화면에 그려지고(렌더링되고),
그 후에 데이터를 가져와서 다시 업데이트해요.
이런 과정을 통해 사용자에게 더 빠르게 화면을 보여줄 수 있어요.
2. 사용자 경험 (UX) 개선
만약 데이터가 다 준비될 때까지 렌더링을 기다린다면,
사용자는 빈 화면을 보게 되고, 화면이 늦게 로딩된다고 느낄 수 있어요.
그래서 데이터를 가져오기 전에라도 컴포넌트를 먼저 렌더링하고,
로딩 상태나 임시 데이터를 보여주는 것이 더 나은 사용자 경험(UX)을 제공해요.
예를 들어:
로딩 상태: 데이터를 아직 못 가져왔을 때,
"로딩 중..."이라는 메시지를 먼저 보여주는 경우를 자주 보죠.
이게 바로 데이터를 가져오기 전에 렌더링하는 이유 중 하나예요.
임시 페이지 수: 마찬가지로, 총 페이지 수를 아직 모를 때라도 임시값으로 1페이지를 먼저 보여주고,
데이터를 받은 후에 정확한 페이지 수로 업데이트할 수 있어요.
3. 비동기 데이터 가져오기 (useEffect)
React의 useEffect 훅을 사용해서 데이터를 가져오는 경우,
데이터 요청은 비동기적으로 처리돼요.
즉, 컴포넌트는 일단 렌더링된 다음에,
useEffect 안에서 서버로부터 데이터를 요청하는 거예요.
이 과정에서 데이터가 도착할 때까지는 시간이 걸리기 때문에,
그 시간 동안에도 화면을 보여줘야 해요.
이를 위해 미리 컴포넌트를 렌더링하는 거죠.
요약
React의 비동기 처리: 컴포넌트는 데이터를 기다리지 않고 먼저 렌더링돼요.
사용자 경험 개선: 사용자가 빈 화면을 보지 않고,
로딩 상태나 임시값을 먼저 볼 수 있게 돼요.
데이터 도착 후 업데이트: 데이터가 도착하면, 그때 다시 정확한 페이지 수로 업데이트해요.
이 방식 덕분에 사용자에게 더 빠른 반응성을 제공할 수 있고, 전체적인 사용자 경험을 개선할 수 있어요.
동기적 & 비동기적
동기적 (Synchronous)
동기적이란 일이 순차적으로 일어나는 걸 의미.
즉, 한 가지 일이 끝나기 전까지 다음 일은 시작하지 않음을 뜻함.
예를 들어, 친구가 메시지를 보내면 네가 그 메시지를 읽고 답장을 보내는 것까지가 하나의 순서야.
답장을 보내기 전에는 다른 일을 하지 않지.
프로그래밍에서는, 동기적 작업은 하나의 작업이 끝날 때까지 코드의 다음 부분이 실행되지 않는 것을 의미.
예를 들어, 데이터를 서버에서 가져오는 작업이 동기적이면,
데이터가 완전히 올 때까지 기다린 후에야 다음 코드를 실행할 수 있음.
비동기적 (Asynchronous)
비동기적은 여러 가지 일을 동시에 처리하는 걸 의미.
즉, 한 가지 일이 끝나지 않아도 다른 일이 먼저 실행될 수 있음을 뜻함.
예를 들어, 친구한테 메시지를 보내놓고, 친구가 답장하기 전에 다른 친구한테 전화할 수도 있잖아.
두 가지 일이 동시에 일어나는 거지.
프로그래밍에서는, 비동기적 작업은 기다리지 않고 다른 작업을 먼저 진행할 수 있게 해줌.
예를 들어, 서버에 데이터를 요청해놓고 데이터를 기다리는 동안 다른 코드를 먼저 실행하는 것.
데이터가 준비되면 그때 서버에서 받은 데이터를 처리하는 코드를 실행하게 돼.
React에서 useEffect로 데이터를 요청할 때,
그 데이터는 비동기적으로 받아와서, 데이터가 오기 전에 컴포넌트가 먼저 렌더링될 수 있음.
그래서 데이터가 다 오기 전에 컴포넌트가 동작하게 하려면 임시값을 설정해줘야 하는 것.
2) 총 페이지 수 업데이트 함수 추가
// 총 페이지 수 업데이트
function updateTotalPages(pages) {
setTotalPages(pages);
}
총 페이지 수를 계산하는 원리:
아이템의 개수를 보고,
한 페이지에 보여줄 아이템 개수를 나눠서 필요한 페이지 수를 계산
한 페이지에 9개의 아이템을 보여주기로 정했으니까
총 아이템 수를 한 페이지에 보여줄 아이템 수로 나누면 총 페이지 수가 나옴.
페이지 업데이트 함수를 추가한 이유:
데이터를 받아오고 나서 총 몇 페이지가 필요한지를 동적으로 계산하고,
그 값을 React 상태로 저장하기 위해서
1. 데이터가 없을 때는 총 페이지 수를 모름
처음 FingerPage 컴포넌트가 렌더링될 때,
아이템 데이터를 아직 받아오지 않았기 때문에 총 몇 페이지가 필요한지 알 수 없음.
예를 들어, 서버에서 가져올 아이템이 10개인지, 20개인지 모르는 상태.
2. 데이터를 받아오고 나서야 총 페이지 수를 알 수 있음
데이터가 서버에서 모두 받아진 후에, 비로소 아이템이 몇 개인지 알 수 있음.
그때 아이템의 개수를 보고 "총 몇 페이지가 필요한지" 계산해야 함.
3. 총 페이지 수를 업데이트하기 위해 함수가 필요함
function updateTotalPages(pages) {
setTotalPages(pages); // 부모 컴포넌트의 totalPages 상태를 업데이트
}
FingerPage는 그 함수를 FingerItems에 다시 props로 넘겨줘서,
FingerItems가 데이터 받아온 후에
총 페이지 수를 계산하고 부모 컴포넌트 (App.js)에 알려주도록 함.
함수를 추가하는 이유
함수의 역할
**updateTotalPages(pages)**라는 함수는 부모 컴포넌트인 **App.js**에서
**setTotalPages(pages)**를 호출하는 역할.
이 함수가 있으면,
자식 컴포넌트인 **FingerItems**가 계산된 페이지 수를 부모 컴포넌트에 쉽게 전달할 수 있다.
데이터를 받아온 후**FingerItems**에서 데이터가 로드되면, 총 페이지 수를 계산하고 **updateTotalPages(totalPages)**를 호출합니다. 이때 총 페이지 수를 인자로 넘겨줌.
3) Routes 안에 있는 Route 수정해주기 = props 로 각 페이지들에게 건네줌.
<Routes>
<Route
path="/"
element={
<MainPage
Search={Search}
setSearch={setSearch}
isShopHovered={isShopHovered}
setIsShopHovered={setIsShopHovered}
isBoardHovered={isBoardHovered}
setIsBoardHovered={setIsBoardHovered}
/>
}
/>
<Route path="/shop" element={<Navigate to="/shop/fingers" replace />} />
<Route
path="/shop/fingers"
element={
<FingerPage
Search={Search}
setSearch={setSearch}
isShopHovered={isShopHovered}
setIsShopHovered={setIsShopHovered}
isBoardHovered={isBoardHovered}
setIsBoardHovered={setIsBoardHovered}
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
updateTotalPages={updateTotalPages}
/>
}
/>
<Route
path="/shop/hair"
element={
<HairPage
Search={Search}
setSearch={setSearch}
isShopHovered={isShopHovered}
setIsShopHovered={setIsShopHovered}
isBoardHovered={isBoardHovered}
setIsBoardHovered={setIsBoardHovered}
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
updateTotalPages={updateTotalPages}
/>
}
/>
<Route
path="/shop/neck"
element={
<NeckPage
Search={Search}
setSearch={setSearch}
isShopHovered={isShopHovered}
setIsShopHovered={setIsShopHovered}
isBoardHovered={isBoardHovered}
setIsBoardHovered={setIsBoardHovered}
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
updateTotalPages={updateTotalPages}
/>
}
/>
<Route path="*" element={<div>없는페이지</div>} />
</Routes>
totalPages 는 예전에도 건네줬었고
updataTotalPages 함수만 props 로 추가해서 건네주면 됨.
각 Page 컴포넌트 수정
- 페이지 컴포넌트
- 아이템 컴포넌트
코드의 흐름
- 데이터 로드하기
- **useEffect**를 사용해서 컴포넌트가 처음 렌더링될 때 데이터를 받아옴.
- 총 페이지 수 계산하기
- 데이터를 성공적으로 받아온 후, 총 페이지 수를 계산.
- 이 계산은 받아온 데이터의 길이를 아이템 수로 나눠서 계산.
- 부모 컴포넌트에 총 페이지 수 전달하기
- 계산된 총 페이지 수를 updateTotalPages 함수를 호출하여 부모 컴포넌트에 전달.
1) FingerPage에서 updateTotalPages()를 FingerItems에 전달
= 페이지컴포넌트 -> 아이템 컴포넌트
<FingerItems
currentPage={props.currentPage}
itemsPerPage={9}
updateTotalPages={props.updateTotalPages}
/>
updateTotalPages={props.updateTotalPages} 이거 추가로 전달해준다.
2) 아이템 컴포넌트 수정
useEffect(() => {
axios
.get("https://kku-git.github.io/nff_product/fingers.json")
.then((response) => {
const fetchedItems = response.data;
setItems(fetchedItems);
// 총 페이지 수 계산 및 업데이트
const totalPages = Math.ceil(fetchedItems.length / itemsPerPage);
updateTotalPages(totalPages); // 부모 컴포넌트에 페이지 수 업데이트
})
.catch(() => {
console.log("실패함");
});
}, [itemsPerPage, updateTotalPages]);
useEffect 부분 수정
기존 코드는 바로 setItems(response.data)를 호출하는 방식
수정된 코드는 const fetchedItems = response.data;를 사용해서
데이터를 변수에 한 번 담은 후 setItems(fetchedItems);를 호출하는 방식
수정한 이유:
const totalPages = Math.ceil(fetchedItems.length / itemsPerPage);
이 부분 작성할 때
변수에 담아주지 않는다면
useEffect(() => {
axios
.get("https://kku-git.github.io/nff_product/fingers.json")
.then((response) => {
setItems(response.data);
// 총 페이지 수 계산 및 업데이트
const totalPages = Math.ceil(response.data.length / itemsPerPage);
updateTotalPages(totalPages); // 부모 컴포넌트에 페이지 수 업데이트
})
.catch(() => {
console.log("실패함");
});
}, [itemsPerPage, updateTotalPages]);
기존 코드인 여기서
const totalPages = Math.ceil(response.data.length / itemsPerPage);
이런 식으로 가독성이 좀 떨어짐.
코드 설명
useEffect(() => {
axios
.get("https://kku-git.github.io/nff_product/fingers.json")
.then((response) => {
const fetchedItems = response.data;
setItems(fetchedItems);
// 총 페이지 수 계산 및 업데이트
const totalPages = Math.ceil(fetchedItems.length / itemsPerPage);
updateTotalPages(totalPages); // 부모 컴포넌트에 페이지 수 업데이트
})
.catch(() => {
console.log("실패함");
});
}, [itemsPerPage, updateTotalPages]);
- 데이터 가져오기:
- axios.get으로 데이터를 가져옴.
- 데이터를 성공적으로 받아오면 fetchedItems(변수)에 저장하고 관리.
- 총 페이지 수 계산:
- Math.ceil(fetchedItems.length / itemsPerPage)로 총 페이지 수를 계산.
- 여기서 fetchedItems.length는 받아온 데이터의 수이고,
- itemsPerPage는 한 페이지에 표시할 아이템 수(9).
- updateTotalPages 호출:
- 계산한 총 페이지 수를 updateTotalPages(totalPages)로 부모 컴포넌트에 전달함.
- 이를 통해 부모 컴포넌트의 상태가 업데이트되어 새로운 총 페이지 수를 반영.
업데이트 과정
const totalPages = Math.ceil(fetchedItems.length / itemsPerPage);
updateTotalPages(totalPages); // 부모 컴포넌트에 페이지 수 업데이트
totalPages 변수에
Math.ceil(fetchedItems.length / itemsPerPage)의 결과를 저장한 다음,
값을 updateTotalPages(totalPages)로 호출하여
App.js에 있는 총 페이지 수 업데이트 함수에 전달
setTotalPages(pages)가 실행되면,
React는 totalPages 상태를 새로운 값으로 업데이트 함.
이 업데이트는 해당 컴포넌트를 리렌더링하게 하여, UI에서 변경된 페이지 수를 반영.
useEffect의 두 번째 인자로 전달된 배열 **[itemsPerPage, updateTotalPages] 에 대해서
itemsPerPage나 updateTotalPages 중 하나라도 변경되면, 해당 useEffect가 다시 실행됨.
itemsPerPage의 값이 변경되면, 아이템 수에 따라 총 페이지 수를 다시 계산해야 하므로 useEffect가 실행
itemsPerPage:
itemsPerPage는 한 페이지에 몇 개의 아이템을 보여줄지를 정하는 값.
만약 이 숫자가 바뀐다면 (예를 들어, 9개에서 12개로 바뀐다면) 총 페이지 수도 달라져야 함.
왜냐하면 같은 수의 아이템을 더 많이 한 페이지에 보여줄 수 있으면 페이지 수가 줄어들기 때문.
- itemsPerPage가 9이면 총 3페이지가 필요하고,
- itemsPerPage가 12로 바뀌면 총 2페이지만 필요함.
itemsPerPage가 변경되면 다시 페이지 수를 계산해야 하기 때문에 이걸 의존성 배열에 넣음.
updateTotalPages:
updateTotalPages는 부모 컴포넌트(App.js)에 있는 페이지 수를 업데이트하는 함수.
함수가 변경될 수 있는 특수한 상황 때문에 집어넣은 것
조건에 따라 다른 함수를 전달해야 하는 상황.
프로젝트가 커지면서 코드를 리팩토링하거나 더 복잡한 동작을 추가해야 하는 상황.
근데 굳이 안써도 될 듯. 변경될 상황이 흔치 않음.
Math.ceil() 함수에
대해서 알아보기
- Math.ceil() 함수는 주어진 숫자를 올림해서 정수로 만들어 주는 함수입니다.
- 예를 들어, 소수점이 있는 숫자를 주면 그 숫자를 가장 가까운 큰 정수로 올려줍니다.
예시
- 정수일 때:
- Math.ceil(5)의 결과는 5입니다. (이미 정수라서 변하지 않음)
- 소수점이 있을 때:
- Math.ceil(5.1)의 결과는 6입니다. (5.1은 5보다 크지만 6보다 작으므로 올림하여 6)
- Math.ceil(3.9)의 결과도 4입니다. (3.9는 3보다 크고 4보다 작으므로 올림하여 4)
- 음수일 때:
- Math.ceil(-2.3)의 결과는 -2입니다. (음수에서 가장 가까운 큰 정수)
- Math.ceil(-1.5)의 결과는 -1입니다. (-1.5는 -2보다 크고 -1보다 작으므로 올림하여 -1)
코드를 예시로 보기 >>>>>>
totalPages = fetchedItems.length / itemsPerPage;
totalPages = 20 / 9; // 결과는 약 2.22
아이템이 20개 있고 한 페이지에 9개를 보여준다면, 다음과 같은 계산이 된다.
- Math.ceil(20 / 9) = 3 페이지
나눈 결과는 약 2.22
하지만 페이지 수는 항상 정수여야 하므로, 이 값을 올림 처리
**Math.ceil**을 사용한 이유는
소수점이 있으면 올림 처리하기 위해서.
왜냐하면 2.22 페이지 같은 건 없으니까,
올림 처리해서 3 페이지로 만들어 줘야 함.
요약
- Math.ceil() 함수는 주어진 숫자를 가장 가까운 큰 정수로 올림 처리합니다.
- 주로 페이지 수 계산처럼 정수를 필요로 할 때 사용됩니다.
- 예를 들어, 아이템을 여러 페이지에 나누어 보여줄 때 페이지 수를 정확하게 계산할 수 있게 해줍니다.
'> 메모 > React' 카테고리의 다른 글
리액트 상단 로고, 제목 바꾸는 방법 (3) | 2024.10.12 |
---|---|
[ JS/리액트 ] 페이지 생성 정리 (2) | 2024.10.04 |
[ JS/리액트 ] axios 써서 데이터 넣는 방법 (1) | 2024.10.03 |
[JS/리액트 ] 헷갈리는 인수와 인자 (1) | 2024.10.02 |
[리액트] Link와 useNavigate 비교 (0) | 2024.09.27 |