๐ก ๊ธฐ์กด ์์ํ ํ์ด์ง ์ฒ๋ฆฌ๋ฅผ ์ ํ๋ธ ๋๊ธ๊ณผ ๋น์ทํ๊ฒ ๋ฌดํ ์คํฌ๋กค๋ก ๊ตฌํํ์๊ณ ์ด๋ ์ค์ง Zustand ์ IntersectionObserver๋ฅผ ์ฌ์ฉํ์์ต๋๋ค.
Zustand Setting
๋๊ธ ๋ฆฌ์คํธ๋ฅผ ๊ด๋ฆฌํ ๊ฐ์ฒด๋ค๊ณผ ์์ฒญ์ ๋ํ ํ๋๊ทธ๋ค์ ์ ์ธํ์ต๋๋ค. ์ด๋ฏธ ๋๊ธ์ ๋ถ๋ฌ์ค๋ ์ค ํน์ ๋ค์์ ๋ถ๋ฌ์ฌ ๋๊ธ ๋ฆฌ์คํธ๊ฐ ์๋ค๋ฉด ์์ฒญ์ ๋ฉ์ถฐ์ผ ํฉ๋๋ค. ๋ํ ์ ์ญ์ผ๋ก ์ํ๋ฅผ ๊ด๋ฆฌํ๊ธฐ ๋๋ฌธ์ ํ์ด์ง๊ฐ ์ฎ๊ฒจ์ก์ ๋ ๋๊ธ ๋ฆฌ์คํธ๋ฅผ ๋น์์ผ ํจ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ Store๊ฐ ๋ง๋ค์ด์ง๋๋ค.
import { create } from 'zustand';
import { APICommentType, BoardCommentType } from '../type/BoardType.ts';
import { client } from '../common/axios.ts';
interface CommentStoreType {
// ๋๊ธ ๋ฆฌ์คํธ State
commentList: BoardCommentType[];
// ํ์ฌ ์ด๋ค ํ์ด์ง์ธ์ง ๊ด๋ฆฌ
page: number;
// ๋๊ธ์ ๋ถ๋ฌ์ค๋์ค ์ธ์ง ์๋์ง
isFetching: boolean;
// ๋ค์ ํ์ด์ง๊ฐ ์๋์ง ์๋์ง
hasNextPage: boolean;
// ๊ฒ์๋ฌผ ๋ณ๊ฒฝ ์ ๋๊ธ ๋ฆฌ์
ํจ์
resetComment: () => void;
// ๋๊ธ ์์ฒญ API
getCommentList: (boardId: string, page: number) => void;
}
export const CommentStore = create<CommentStoreType>(set => ({
commentList: [],
page: 0,
isFetching: false,
hasNextPage: true,
resetComment: () =>
set(() => ({ commentList: [], page: 0, isFetching: false, hasNextPage: true })),
getCommentList: (boardId: string, page: number) => {
// Fetching On
CommentStore.setState(() => ({ isFetching: true }));
client.get<APICommentType>(`boardComment/${boardId}?page=${page}&size=10`).then(res => {
// ๋๊ธ ์์ผ๋ฉด
if (res.findBoardCommentDtos.length === 0) {
CommentStore.setState(() => ({ hasNextPage: false }));
} else {
CommentStore.setState(store => ({
page: store.page + 1,
commentList: [...store.commentList, ...res.findBoardCommentDtos],
isFetching: false,
hasNextPage: true,
}));
}
});
},
}));
Infinite Scroll
ํด๋น ํ์ด์ง์ ์คํฌ๋กค์ ๊ณ์ ์ถ์ ํ๋ ๊ฑด ๋ฆฌ์์ค ๋ญ๋น๊ฐ ์๋ค๊ณ ์๊ฐํ์ฌ IntersectionObserver๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ํ์ด์ง๋ฅผ ๋๋ฌ๋ณด๋ ์ค Target Component๋ฅผ ๋ฐ๊ฒฌ ์ ๋ค์ ๋๊ธ ๋ฆฌ์คํธ๋ฅผ ๋ถ๋ฌ์ค๋๋ก ๊ฐ์ํ์ผ๋ฉฐ isFetching, hasNextPage๋ฅผ ์ฌ์ฉํด API ์์ฒญ์ ๋ถ๊ธฐ ์ฒ๋ฆฌํ์ต๋๋ค.
import styled from '@emotion/styled';
import Comment from './Comment';
import { MutableRefObject, useEffect, useLayoutEffect, useRef } from 'react';
import { CommentStore } from '../../../store/CommentStore.ts';
function PostComment({ boardId }: { boardId: string }) {
const { commentList, page, isFetching, hasNextPage, resetComment, getCommentList } =
CommentStore();
const targetRef = useRef() as MutableRefObject<HTMLDivElement>;
const io = new IntersectionObserver(entries => {
entries.forEach(({ target, isIntersecting }) => {
// target ์กด์ฌ ์ฌ๋ถ ๋ฐ ์์๊ฐ ๋ณด์ฌ์ง๊ณ ์๋์ง ํ์ธ
if (target && isIntersecting) {
getCommentList(boardId, page);
}
});
});
useLayoutEffect(() => {
// targetRef ํ์ฑํ ์ ๊ฐ์ ์์
if (targetRef.current) {
io.observe(targetRef.current);
}
// ํ์ด์ง ๋๊ฐ๋ฉด ๊ฐ์ ์ข
๋ฃ
return () => io.disconnect();
}, [io]);
// ํ์ด์ง ๋ณ๊ฒฝ ์ ๋๊ธ ๋ฆฌ์
useEffect(() => resetComment(), [boardId]);
return (
<CommentWrap>
{commentList.map(cl => {
return <Comment key={cl.boardCommentId} comment={cl} />;
})}
{!isFetching && hasNextPage && <Target ref={targetRef} />}
</CommentWrap>
);
}
const CommentWrap = styled.div`
width: 100%;
`;
const Target = styled.div`
height: 1px;
`;
export default PostComment;
์ดฌ์์ ์ํด ๋ก๋ ์๋์ 2์ด ๊ฐ์ํ์์ต๋๋ค.
'React > TypeScript' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
React Typescript Interface Tuple Generic (0) | 2024.03.25 |
---|---|
React RichTextEditor Image Upload - reactQuill (0) | 2024.03.18 |
React TypeScript useInfiniteQuery Infinite Scroll (0) | 2024.03.15 |
React Axios TypeScript with JWT Boilerplate (0) | 2024.03.14 |
React TypeSciprt - Community Project (0) | 2024.03.13 |