일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 예외미루기
- 다형성
- 추상메서드
- 컬렉션 타입
- 생성자오버로드
- 객체 비교
- 제네릭
- 집합_SET
- 환경설정
- GRANT VIEW
- 예외처리
- 컬렉션프레임워크
- 한국건설관리시스템
- exception
- EnhancedFor
- 오라클
- 인터페이스
- 사용자예외클래스생성
- Java
- 어윈 사용법
- oracle
- cursor문
- 메소드오버로딩
- abstract
- NestedFor
- 대덕인재개발원
- 자동차수리시스템
- 정수형타입
- 자바
- 참조형변수
- Today
- Total
거니의 velog
(18) 프론트엔드 프로젝트 : 포스트 조회 기능 구현하기 5 본문
(4) 페이지네이션 구현하기
* 이번에는 페이지네이션 기능을 구현해 보자. list API를 만들 때 마지막 페이지 번호를 HTTP 헤더를 통해 클라이언트에 전달하도록 설정했다. 그러나 요청을 관리하는 사가를 쉽게 만들기 위해 작성한 createRequestSaga 에서는 SUCCESS 액션을 발생시킬 때 payload에 reponse.data 값만 넣어 주기 때문에 현재 구조로는 헤더를 확인할 수 없다.
* 그렇기 때문에 createRequestSaga를 조금 수정해 줄 것이다.
[lib/createRequestSaga.js]
(...)
export default function createRequestSaga(type, request) {
const SUCCESS = `${type}_SUCCESS`;
const FAILURE = `${type}_FAILURE`;
return function* (action) {
yield put(startLoading(type)); // 로딩 시작
try {
const response = yield call(request, action.payload);
yield put({
type: SUCCESS,
payload: response.data,
meta: response,
});
} catch (e) {
yield put({
type: FAILURE,
payload: e,
error: true,
});
}
yield put(finishLoading(type)); // 로딩 끝
};
}
* 이렇게 액션 안에 meta 값을 response로 넣어 주면 나중에 HTTP 헤더 및 상태 코드를 쉽게 조회할 수 있다.
* posts 리덕스 모듈을 열어서 다음과 같이 수정해 주자.
[modules/posts.js]
(...)
const initialState = {
posts: null,
error: null,
lastPage: 1,
};
const posts = handleActions(
{
[LIST_POSTS_SUCCESS]: (state, { payload: posts, meta: response }) => ({
...state,
posts,
lastPage: parseInt(response.headers['last-page'], 10), // 문자열을 숫자로 변환
}),
[LIST_POSTS_FAILURE]: (state, { payload: error }) => ({
...state,
error,
}),
},
initialState,
);
export default posts;
* 이제 리덕스 스토어 안에 마지막 페이지 번호를 lastPage라는 값으로 담아 둘 수 있다. 페이지네이션을 위한 컴포넌트 Pagination.js를 components/posts 디렉터리에 작성해 보자.
[components/posts/Pagination.js]
import React from 'react';
import styled from 'styled-components';
import qs from 'qs';
import Button from '../common/Button';
const PaginationBlock = styled.div`
width: 320px;
margin: 0 auto;
display: flex;
justify-content: space-between;
margin-bottom: 3rem;
`;
const PageNumber = styled.div``;
const buildLink = ({ username, tag, page }) => {
const query = qs.stringify({ tag, page });
return username ? `/@${username}?${query}` : `/?${query}`;
};
const Pagination = ({ page, lastPage, username, tag }) => {
return (
<PaginationBlock>
<Button
disabled={page === 1}
to={
page === 1 ? undefined : buildLink({ username, tag, page: page - 1 })
}
>
이전
</Button>
<PageNumber>{page}</PageNumber>
<Button
disabled={page === lastPage}
to={
page === lastPage
? undefined
: buildLink({ username, tag, page: page + 1 })
}
>
다음
</Button>
</PaginationBlock>
);
};
export default Pagination;
* 이 컴포넌트에서는 props로 현재 선택된 계정명, 태그, 현재 페이지 숫자, 마지막 페이지 숫자를 가져온다. 사용자가 이 컴포넌트에 있는 버튼을 클릭하면, props로 받아 온 값을 사용하여 이동해야 할 다음 경로를 설정해 준다. 그리고 첫 번째 페이지일 때는 이전 버튼이 비활성화되고, 마지막 페이지일 때는 다음 버튼이 비활성화된다.
* 컴포넌트를 다 만든 뒤에는 Button 컴포넌트에 비활성화된 스타일을 설정해 주자. 비활성화 스타일은 :disabled CSS 셀렉터를 사용하여 적용할 수 있다.
[components/common/Button.js]
import React from 'react';
import styled, { css } from 'styled-components';
import { Link } from 'react-router-dom';
import palette from '../../lib/styles/palette';
const buttonStyle = css`
(...)
&:disabled {
background: ${palette.gray[3]};
color: ${palette.gray[5]};
cursor: not-allowed;
}
`;
(...)
* 버튼 스타일을 수정한 후에는 Pagination 컴포넌트를 위한 컨테이너인 PaginationContainer 컴포넌트를 만들자.
[containers/posts/PaginationContainer.js]
import React from 'react';
import Pagination from '../../components/posts/Pagination';
import { useSelector } from 'react-redux';
import { withRouter } from 'react-router-dom';
import qs from 'qs';
const PaginationContainer = ({ location, match }) => {
const { lastPage, posts, loading } = useSelector(({ posts, loading }) => ({
lastPage: posts.lastPage,
posts: posts.posts,
loading: loading['posts/LIST_POSTS'],
}));
// 포스트 데이터가 없거나 로딩 중이면 아무것도 보여주지 않음
if (!posts || loading) return null;
const { username } = match.params;
// page가 없으면 1을 기본값으로 사용
const { tag, page = 1 } = qs.parse(location.search, {
ignoreQueryPrefix: true,
});
return (
<Pagination
tag={tag}
username={username}
page={parseInt(page, 10)}
lastPage={lastPage}
/>
);
};
export default withRouter(PaginationContainer);
* 다음으로 이 컨테이너 컴포넌트를 PostListPage에서 렌더링해 주면 페이지네이션 기능의 구현이 모두 끝난다.
[pages/PostListPage.js]
import React from 'react';
import HeaderContainer from '../containers/common/HeaderContainer';
import PostListContainer from '../containers/posts/PostListContainer';
import PaginationContainer from '../containers/posts/PaginationContainer';
const PostListPage = () => {
return (
<div>
<HeaderContainer />
<PostListContainer />
<PaginationContainer />
</div>
);
};
export default PostListPage;
* 다음과 같이 페이지 하단에 페이지네이션 UI가 나타났는가? 버튼을 눌러 잘 작동되는지 확인해 보자.
* 첫 번째 페이지일 때는 이전 버튼이 비활성화되고, 마지막 페이지일 때는 다음 버튼이 활성화된다. 계정명이나 태그를 클릭하여 포스트 쿼리 시스템도 잘 작동하는지 확인해 보자.
3. 정리
* 포스트 조회 기능에 대한 구현도 모두 마쳤다! 다음 장에서는 포스트 수정 및 삭제 기능을 구현하고 프로젝트를 마무리 해 볼 것이다.
'React_프론트엔드 프로젝트' 카테고리의 다른 글
(20) 프론트엔드 프로젝트 : 수정/삭제 기능 구현 및 마무리 2 (0) | 2024.02.23 |
---|---|
(19) 프론트엔드 프로젝트 : 수정/삭제 기능 구현 및 마무리 1 (0) | 2024.02.23 |
(17) 프론트엔드 프로젝트 : 포스트 조회 기능 구현하기 4 (0) | 2024.02.23 |
(16) 프론트엔드 프로젝트 : 포스트 조회 기능 구현하기 3 (0) | 2024.02.23 |
(15) 프론트엔드 프로젝트 : 포스트 조회 기능 구현하기 2 (0) | 2024.02.23 |