이 글은 Code with Mosh의 Mastering React 과정을 공부하며 정리한 글입니다. 리알못이라 이상하거나 틀린 내용이 있을 것입니다…
사전 준비
Bootstrap 설치
npm i bootstrap@4.1.1
명령어로 React 프로젝트에 Bootstrap을 설치한 후 index.js 파일에 import 'bootstrap/dist/css/bootstrap.css';
를 추가해줍니다.
lodash 설치
lodash는 자바스크립트 유틸리티 라이브러리입니다. 여기서는 배열을 편집하기 위해 사용합니다. npm i lodash@4.17.10
명령어로 React 프로젝트에 lodash를 설치한 후 lodash를 사용하려는 컴포넌트에서 import _ from 'lodash';
를 추가해줍니다.
Bootstrap을 이용한 간단한 페이지 수 매기기
Bootstrap Pagination 문서를 보면 Pagination 예제를 볼 수 있습니다. 아래는 화면 하단에 간단하게 페이지 수를 매긴 예제입니다.
/***** App.js *****/
import React from 'react';
const App = () => {
return (
<React.Fragment>
<div>안녕요?ㅎ</div>
{/* Pagination */}
<nav aria-label="Page navigation example">
<ul className="pagination">
<li className="page-item"><a className="page-link" href="#">Previous</a></li>
<li className="page-item"><a className="page-link" href="#">1</a></li>
<li className="page-item"><a className="page-link" href="#">2</a></li>
<li className="page-item"><a className="page-link" href="#">3</a></li>
<li className="page-item"><a className="page-link" href="#">Next</a></li>
</ul>
</nav>
</React.Fragment>
);
};
export default App;
아이템 수에 비례하여 페이지 수 표시하기
위의 예제는 정적으로 페이지 수를 매긴 예제입니다. 실제로는 페이지에 보여 줄 아이템 수가 변할 수 있기 때문에 동적으로 페이지 수를 매겨야 합니다. 예를 들어 아이템이 10개가 있고 각 페이지에 보여줄 아이템 수를 3개로 정한다면 3 + 3 + 3 + 1 총 4페이지가 필요하지요.
아래 예제는 페이지에 보여 줄 아이템 수와 한 페이지엡 보여 줄 아이템 수에 따라 하단에 페이지 수를 매기는 예제입니다.
/***** App.js *****/
import React from 'react';
import MoviesPage from './components/MoviesPage';
const App = () => {
return <MoviesPage />;
};
export default App;
/***** ./components/MoviesPage.js *****/
import React, { useState } from 'react';
import Pagination from './common/Pagination';
const MoviesPage = () => {
const getMovies = () => { // 영화 정보를 반환하는 함수
const movies = [
{ id: 0, title: "기생충", genre: "블랙 코미디", release: "2019-05-30" },
{ id: 1, title: "라이온 킹", genre: "애니메이션", release: "2019-07-17" },
{ id: 2, title: "날씨의 아이", genre: "애니메이션", release: "2019-10-31" },
{ id: 3, title: "알라딘", genre: "판타지", release: "2019-05-23" },
{ id: 4, title: "나랏말싸미", genre: "역사", release: "2019-07-24" },
{ id: 5, title: "주전장", genre: "역사", release: "2019-07-25" },
{ id: 6, title: "어벤져스: 엔드게임", genre: "판타지", release: "2019-04-24" },
{ id: 7, title: "봉오동 전투", genre: "역사", release: "2019-08-07" },
{ id: 8, title: "김복동", genre: "역사", release: "2019-08-08" },
{ id: 9, title: "코코", genre: "애니메이션", release: "2018-01-11" },
]
return movies;
}
const [movies, setMovies] = useState({ // 영화 정보를 담는 state
data: getMovies(),
pageSize: 3 // 한 페이지에 보여줄 아이템(영화목록) 개수
});
const { length: count } = movies.data;
if(count === 0)
return <p>영화 정보가 없습니다.</p>
return (
<>
<p>{count} 개의 영화 정보가 있습니다.</p>
<table className="table">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Genre</th>
<th>Release</th>
</tr>
</thead>
<tbody>
{movies.data.map(movie =>
<tr key={movie.id}>
<td>{movie.id}</td>
<td>{movie.title}</td>
<td>{movie.genre}</td>
<td>{movie.release}</td>
</tr>
)}
</tbody>
</table>
<Pagination
itemsCount={count}
pageSize={movies.pageSize}
/>
</>
);
};
export default MoviesPage;
/***** ./components/common/Pagination.js *****/
import React from 'react';
import _ from 'lodash';
const Pagination = (props) => {
const { itemsCount, pageSize } = props; // 각각 아이템(영화목록) 개수, 한 페이지에 보여줄 아이템(영화목록) 개수
const pageCount = Math.ceil(itemsCount / pageSize); // 몇 페이지가 필요한지 계산
if (pageCount === 1) return null; // 1페이지 뿐이라면 페이지 수를 보여주지 않음
const pages = _.range(1, pageCount + 1); // 마지막 페이지에 보여줄 컨텐츠를 위해 +1, https://lodash.com/docs/#range 참고
return (
<nav> {/* VSCode 입력: nav>ul.pagination>li.page-item>a.page-link */}
<ul className="pagination">
{pages.map(page => (
<li key={page} className="page-item" style={{ cursor: "pointer" }}>
<a className="page-link">{page}</a>
</li>
))}
</ul>
</nav>
);
}
export default Pagination;
페이지 변경 이벤트 처리
페이지를 매긴 후엔 현재 페이지 위치를 시작적으로 보여주고 페이지 수를 클릭했을 때 발생한 이벤트를 처리해야 합니다. 아래 예제는 그런 예제입니다. 간단하게 보여주기 위해 위 예제에서 사용한 아이템 추가 코드를 삭제하고 아이템 개수 State로 대체했습니다.
/***** ./components/MoviesPage.js *****/
import React, { useState } from 'react';
import Pagination from './common/Pagination';
const MoviesPage = () => {
const [movies, setMovies] = useState({ // 영화 정보를 담는 state
data: "", // 생략...
pageSize: 2, // 한 페이지에 보여줄 아이템(영화목록) 개수
itemsCount: 10, // 아이템(영화 정보) 개수 (임의로 10으로 설정함)
currentPage: 1 // 현재 활성화된 페이지 위치
});
const handlePageChange = (page) => {
setMovies({ ...movies, currentPage: page });
}
const { pageSize, itemsCount, currentPage } = movies;
return (
<>
<p>(대충 페이지 내용이 들어갈 부분)</p>
<Pagination
pageSize={pageSize}
itemsCount={itemsCount}
currentPage={currentPage}
onPageChange={handlePageChange}
/>
</>
);
};
export default MoviesPage;
/***** ./components/common/Pagination.js *****/
import React from 'react';
import _ from 'lodash';
const Pagination = (props) => {
const { itemsCount, pageSize, currentPage, onPageChange } = props; // 각각 아이템(영화목록) 개수, 한 페이지에 보여줄 아이템(영화목록) 개수
const pageCount = Math.ceil(itemsCount / pageSize); // 몇 페이지가 필요한지 계산
if (pageCount === 1) return null; // 1페이지 뿐이라면 페이지 수를 보여주지 않음
const pages = _.range(1, pageCount + 1); // 마지막 페이지에 보여줄 컨텐츠를 위해 +1, https://lodash.com/docs/#range 참고
return (
<nav> {/* VSCode 입력: nav>ul.pagination>li.page-item>a.page-link */}
<ul className="pagination">
{pages.map(page => (
<li
key={page}
className={page === currentPage ? "page-item active" : "page-item"} // Bootstrap을 이용하여 현재 페이지를 시각적으로 표시
style={{ cursor: "pointer" }}>
<a className="page-link" onClick={() => onPageChange(page)}>{page}</a> {/* 페이지 번호 클릭 이벤트 처리기 지정 */}
</li>
))}
</ul>
</nav>
);
}
export default Pagination;
- 출력화면
페이지 별로 아이템 보여주기
지금까지의 예제는 어떤 페이지 수를 클릭해도 모든 아이템을 보여줬습니다. 실제로는 페이지 별로 다른 아이템을 보여줘야 합니다. 이를 위해 lodash를 사용하여 배열을 잘라 각 페이지 별로 아이템이 속한 배열을 얻어옵니다.
/***** ../utils/paginate.js *****/
import _ from 'lodash';
export function paginate(items, pageNumber, pageSize) {
const startIndex = (pageNumber - 1) * pageSize; // 자를 배열의 시작점
return _(items)
.slice(startIndex) // 시작점부터 배열을 자르되
.take(pageSize) // pageSize만큼의 배열을 취함
.value(); // lodash wrapper 객체를 regular 배열로 변환
}
/***** ./components/MoviesPage.jsx *****/
import React, { useState } from 'react';
import Pagination from './common/Pagination';
import { paginate } from '../utils/paginate';
const MoviesPage = () => {
const getMovies = () => {
const movies = [
{ id: 0, title: "기생충", genre: "블랙 코미디", release: "2019-05-30" },
{ id: 1, title: "라이온 킹", genre: "애니메이션", release: "2019-07-17" },
{ id: 2, title: "날씨의 아이", genre: "애니메이션", release: "2019-10-31" },
{ id: 3, title: "알라딘", genre: "판타지", release: "2019-05-23" },
{ id: 4, title: "나랏말싸미", genre: "역사", release: "2019-07-24" },
{ id: 5, title: "주전장", genre: "역사", release: "2019-07-25" },
{ id: 6, title: "어벤져스: 엔드게임", genre: "판타지", release: "2019-04-24" },
{ id: 7, title: "봉오동 전투", genre: "역사", release: "2019-08-07" },
{ id: 8, title: "김복동", genre: "역사", release: "2019-08-08" },
{ id: 9, title: "코코", genre: "애니메이션", release: "2018-01-11" },
]
return movies;
}
const [movies, setMovies] = useState({ // 영화 정보를 담는 state
data: getMovies(), // 영화 정보
pageSize: 3, // 한 페이지에 보여줄 아이템(영화목록) 개수
currentPage: 1 // 현재 활성화된 페이지 위치
});
const handlePageChange = (page) => {
setMovies({ ...movies, currentPage: page });
};
const { data, pageSize, currentPage } = movies;
const pagedMovies = paginate(data, currentPage, pageSize); // 페이지 별로 아이템이 속한 배열을 얻어옴
const { length: count } = movies.data;
if (count === 0)
return <p>영화 정보가 없습니다.</p>;
return (
<>
<p>{count} 개의 영화 정보가 있습니다.</p>
<table className="table">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Genre</th>
<th>Release</th>
</tr>
</thead>
<tbody>
{pagedMovies.map((movie) => (
<tr key={movie.id}>
<td>{movie.id}</td>
<td>{movie.title}</td>
<td>{movie.genre}</td>
<td>{movie.release}</td>
</tr>
))}
</tbody>
</table>
<Pagination
pageSize={pageSize}
itemsCount={count}
currentPage={currentPage}
onPageChange={handlePageChange}
/>
</>
);
};
export default MoviesPage;
- ./components/common/Pagination.jsx
import React from 'react';
import _ from 'lodash';
const Pagination = (props) => {
const { itemsCount, pageSize, currentPage, onPageChange } = props; // 각각 아이템(영화목록) 개수, 한 페이지에 보여줄 아이템(영화목록) 개수
const pageCount = Math.ceil(itemsCount / pageSize); // 몇 페이지가 필요한지 계산
if (pageCount === 1) return null; // 1페이지 뿐이라면 페이지 수를 보여주지 않음
const pages = _.range(1, pageCount + 1); // 마지막 페이지에 보여줄 컨텐츠를 위해 +1, https://lodash.com/docs/#range 참고
return (
<nav> {/* VSCode 입력: nav>ul.pagination>li.page-item>a.page-link */}
<ul className="pagination">
{pages.map(page => (
<li
key={page}
className={page === currentPage ? "page-item active" : "page-item"} // Bootstrap을 이용하여 현재 페이지를 시각적으로 표시
style={{ cursor: "pointer" }}>
<a className="page-link" onClick={() => onPageChange(page)}>{page}</a> {/* 페이지 번호 클릭 이벤트 처리기 지정 */}
</li>
))}
</ul>
</nav>
);
}
export default Pagination;
- 실행화면
타입 체크(Type Checking)
props에 엉뚱한 값을 전달하면 에러가 발생하지 않지만 엉뚱한 결과가 표시될 수 있습니다. 예를 들어 숫자를 전달해야 하지만 문자열을 넘기는 경우 콘솔에 경고나 에러가 발생하지 않을 수 있습니다. 이를 막거나 버그를 잡기 쉽도록 타입 체크가 필요합니다.
이를 위해 PropTypes라는 라이브러리를 사용합니다. 이 라이브러리는 리액트 버전 15 이후 별도로 설치하여 사용해야 합니다. 설치 명령어는 npm i prop-types@15.6.2
이며, 사용하려는 컴포넌트에서 import PropTypes from 'prop-types';
코드로 import 후 사용하면 됩니다.
PropTypes에 대한 구체적인 사용 법은 https://reactjs.org/docs/typechecking-with-proptypes.html 을 참고하시기 바랍니다. PropTypes 라이브러리를 사용함으로써 render() 함수 내에 여기저기 사용된 props를 찾아보지 않고 별도로 코딩한 PropTypes 부분만 보면 되므로 좀 더 편할 수 있습니다.
/***** App.js *****/
import React from 'react';
import MyComponent from './components/MyComponent';
const App = () => {
return <MyComponent title="안녕요?ㅎ" year={2020} />;
};
export default App;
/***** ./components/myComponent.js *****/
import React from 'react';
import PropTypes from 'prop-types'; // PropTypes import
const MyComponent = (props) => {
const { title, year } = props;
return (
<React.Fragment>
<h1>{title}</h1>
<p>올해는 {year}년입니다.</p>
</React.Fragment>
);
}
// 타입 체크
MyComponent.propTypes = {
title: PropTypes.string.isRequired,
year: PropTypes.number.isRequired
}
export default MyComponent;
위 코드 중 App.js에서 year를 {2019}가 아닌 "2019"로 지정하면 숫자가 아닌 문자열을 넘기게 됩니다. MyComponent.jsx에서 year의 타입은 number
임을 명시했으므로 콘솔에 다음과 같은 경고가 뜨게 됩니다. 마찬가지로 title과 year는 isRequired
이므로 값을 지정하지 않으면 콘솔에 경고가 발생하게 됩니다.
리액트로 개발하면서 페이징처리하고 있는데 많은 도움이 되었습니다. 정말 감사합니다.
정말 감사합니다 !! 큰 도움이 되었습니다 ^^