Assignment 3 과제를 통해 얻은 지식 정리 글입니다.
과제 레파지토리 : https://github.com/POB-Coconut/Assignment-3
이번 기업 과제는 요구 사항이 많아 2번에 걸쳐 제출하는 방식입니다.
저는 이번에 관리자 로그인을 하면 등록한 계정 정보를 볼 수 있는 테이블 컴포넌트, 테이블 페이지 네이션, utils 함수 분리 및 jest를 이용한 test 작성을 맡았습니다. 사다리 타기로.. 이번 글은 테이블 컴포넌트, 테이블 페이지 네이션에 대한 글 입니다.
유저 데이터 테이블이기에, 테이블이 길어질 수 있다 판단해 페이지 네이션을 이용해 구현하기로 설계했습니다. 또 페이지가 길어질 수 있어 페이지를 숨기는 기능을 구현해봤습니다.
유저 정보를 list로 구현할 수도 있지만 요구 사항인 table 태그와 시멘틱 태그를 이용해 구현했습니다.
<thead>
태그에 요소들을 대표하는 행을, <tbody>
에 정보들을 작성했습니다.
const UserTable = () => {
return (
<>
<TableContainer>
<thead>
<tr>
<th>아이디</th>
<th>이름</th>
<th>나이</th>
<th>주소</th>
<th>카드 번호</th>
<th>권한</th>
</tr>
</thead>
<tbody>
<UserData userData={currentUserData} />
</tbody>
</TableContainer>
<Pagination
userDataPerPage={DATA_PER_PAGE}
totalUserData={userData.length}
currentPage={currentPage}
paginate={setCurrentPage}
/>
</>
);
};
<tbody>
태그에 해당하는 내용입니다. <tbody>
태그엔 &&
을 쓸 수 없어 삼항연산자로 표현했습니다.
리팩토링 과정을 거쳐 수정했습니다.
const UserData = ({ userData }) => {
return (
<>
{userData.map((data) => (
<tr key={data.id}>
<td>{data.id}</td>
<td>{data.name}</td>
<td>{data.age}</td>
<td>{data.address}</td>
<td>{data.cardNumber}</td>
<td>{data.userType}</td>
</tr>
))
: null}
</>
);
};
페이지네이션을 하기 위해선 페이지네이션 컴포넌트 생성과 데이터를 자르는 작업을 거쳐야 합니다.
데이터를 자르는는 로직입니다.
const UserTable = () => {
1️⃣ 상태관리
const [userData, setUserData] = useState([]);
const [currentUserData, setCurrentUserData] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [firstIndex, setFirstIndex] = useState(null);
const [lastIndex, setLastIndex] = useState(null);
// 과제 특성상 localStorage에 데이터를 저장해놨습니다.
// localStorage에서 데이터 불러오는 로직
useEffect(() => {
setUserData(getStoreage(GET_USER_STORAGE_KEYWARD));
}, []);
2️⃣ 현재 페이지에 보여줄 데이터의 index 관련
useEffect(() => {
const nextLastIndex = currentPage * DATA_PER_PAGE;
const nextFirstIndex = nextLastIndex - DATA_PER_PAGE;
setLastIndex(nextLastIndex);
setFirstIndex(nextFirstIndex);
}, [currentPage, lastIndex]);
3️⃣ 현재 페이지에 보여줄 데이터 set
useEffect(() => {
setCurrentUserData(currentUsers(userData, firstIndex, lastIndex));
}, [userData, firstIndex, lastIndex]);
if (userData.length === 0) {
return <p>데이터가 비어 있습니다.</p>;
}
return (
<>
<TableContainer>
<thead>
<tr>
<th>아이디</th>
<th>이름</th>
<th>나이</th>
<th>주소</th>
<th>카드 번호</th>
<th>권한</th>
</tr>
</thead>
<tbody>
4️⃣ <UserData userData={currentUserData} />
</tbody>
</TableContainer>
5️⃣ <Pagination
totalUserData={userData.length}
currentPage={currentPage}
paginate={setCurrentPage}
/>
</>
);
};
1. 상태 관리 userData
userData
: 가지고 있는 총 데이터.
currentUserData
: 현재 페이지에 보여줄 데이터.
currentPage
: 현재 페이지 숫자.
firstIndex
: 현재 페이지에 보여줄 데이터의 첫 번째 index.
lastIndex
: 현재 페이지에 보여줄 데이터의 마지막 index.
2. 현재 페이지에 보여줄 데이터의 index 관련
DATA_PER_PAGE
: 한 페이지에 보여줄 데이터의 개수를 저장한 상수입니다.
nextLastIndex : 현재 페이지에 DATA_PER_PAGE
를 곱한 값. 즉, 마지막 index
nextFirstIndex : 마지막 index에 DATA_PER_PAGE
를 뺀 값. 즉, 첫 번째 index
3. 현재 페이지에 보여줄 데이터 set
currentUsers 함수: slice를 통해 나눈 data를 반환합니다.
export const currentUsers = (data, indexOfFirst, indexOfLast) => {
const currentPageUsers = data.slice(indexOfFirst, indexOfLast);
return currentPageUsers;
};
4. 나눈 데이터 display
5. Pagination을 위한 props
Pagination 컴포넌트에선 Table 컴포넌트에서
const Pagination = ({ totalDataNum, paginate, currentPage }) => {
1️⃣ 페이지 리스트 저장할 state
const [pageLists, setPageLists] = useState([]);
2️⃣ 총 페이지 숫자
const totalPageNum = useMemo(
() => getTotalPageNum(totalDataNum, DATA_PER_PAGE),
[totalDataNum]
);
useEffect(() => {
3️⃣ 페이지 리스트 생성
const totalPageList = Array.from({ length: totalPageNum }, (v, i) => i + 1);
4️⃣ 페이지 리스트 자르는 로직
if (currentPage <= ONE_WAY_MIN_PAGE_NUM) {
const displayedPageList = totalPageList.slice(0, ONE_WAY_MIN_PAGE_NUM + 2);
setPageLists(displayedPageList);
} else if (currentPage >= totalPageNum - 2) {
const displayedPageList = totalPageList.slice(totalPageNum - 5, totalPageNum);
setPageLists(displayedPageList);
} else {
const displayedPageList = totalPageList.slice(
currentPage - ONE_WAY_MIN_PAGE_NUM,
currentPage + ONE_WAY_MIN_PAGE_NUM - 1,
);
setPageLists(displayedPageList);
}
}, [totalPageNum, currentPage]);
5️⃣ 끝 화살표 이동 함수
const goEdgePage = useCallback(
(edgePage) => {
paginate(edgePage);
},
[paginate]
);
6️⃣ 이전, 다음 화살표 이동 함수
const goNextToPage = useCallback(
(nextToPage) => {
if (nextToPage < 1 || nextToPage > totalPageNum) return;
paginate(nextToPage);
},
[paginate, totalPageNum]
);
return (
<PaginationContainer>
<p onClick={() => goEdgePage(1)}>{"|<"}</p>
<p onClick={() => goNextToPage(currentPage - 1)}>{"<"}</p>
<PageUl>
{pageLists.map((number) => (
<PageLi key={number} active={currentPage === number}>
<p onClick={() => paginate(number)}>{number}</p>
</PageLi>
))}
</PageUl>
<p onClick={() => goNextToPage(currentPage + 1)}>{">"}</p>
<p onClick={() => goEdgePage(totalPageNum)}>{">|"}</p>
</PaginationContainer>
);
};
1. 페이지 리스트 저장할 state
페이지 리스트들을 담을 변수입니다.
2. 총 페이지 숫자
페이지 리스트의 최댓값을 구합니다. useMemo
를 이용해 재연산을 막았습니다.
const totalPageNum = useMemo(
() => getTotalPageNum(totalDataNum, DATA_PER_PAGE),
[totalDataNum]
);
getTotalPageNum 함수
: 총 데이터 수를 DATA_PER_PAGE
로 나눈 고 오름 후 반환.
export const getTotalPageNum = (totalUserData, DATA_PER_PAGE) => {
return Math.ceil(totalUserData / DATA_PER_PAGE);
};
3. 페이지 리스트 생성
totalPageList
: totalPageNum
의 수만큼의 배열 생성 후, 차례대로 index+1 씩 저장
// [1,2,3,4,5,6,7...]
const totalPageList = Array.from({ length: totalPageNum }, (v, i) => i + 1);
4. 페이지 리스트 자르는 로직
페이지 리스트가 너무 길 때를 대비해서 자르는 로직입니다.
ONE_WAY_MIN_PAGE_NUM
는 한 방향으로 표현할 수 있는 페이지 리스트의 최소수 +1입니다.
ONE_WAY_MIN_PAGE_NUM가 3라면 이런 식으로...
|< < 1 2 3 4 5> >|
|< < 2 3 4 5 6 > >|
현재 페이지가 ONE_WAY_MIN_PAGE_NUM
보다 작다면, 페이지 리스트를 0~ONE_WAY_MIN_PAGE_NUM+2
까지 자릅니다.
if (currentPage <= ONE_WAY_MIN_PAGE_NUM) {
const displayedPageList = totalPageList.slice(0, currentPage + ONE_WAY_MIN_PAGE_NUM +2);
이런 경우에 해당됩니다.
|< < 1️ 2 3 4 5 > >|
현재 페이지가 ONE_WAY_MIN_PAGE_NUM
보다 크다면, 페이지 리스트를 totalPageNum - 5
, totalPageNum
까지 자릅니다.
이런 경우에 해당됩니다.
|< < 5 6 7 8 9 > >|
그 외 경우 가운데를 중심으로 양옆 2개씩 페이지 리스트를 표시합니다.
else if (currentPage > ONE_WAY_MIN_PAGE_NUM) {
const displayedPageList = totalPageList.slice(
currentPage - ONE_WAY_MIN_PAGE_NUM,
currentPage + ONE_WAY_MIN_PAGE_NUM - 1,
);
5. 끝 화살표 이동 함수
edgePage
: 처음이거나 마지막 페이지의 숫자를 인자로 받아와 currentPage를
설정합니다.
6. 이전, 다음 화살표 이동 함수
nextToPage
: 이전 혹은 이후의 페이지의 숫자를 인자로 받아와 양옆의 currentPage를
설정합니다.
페이지네이션이라고 하면 백엔드에서 구현하고 프론트엔드에서 page와 DATA_PER_PAGE를 이용해 호출하는 방식으로만 사용해봤습니다. 이번 기회에 프론트엔드에서 페이지 네이션 구현과 페이지 리스트를 자르는 로직을 구현하면서 페이지 네이션에 어느 정도 자신이 생긴 것 같습니다.