YU-Quiz / web

front(react)
2 stars 0 forks source link

상황에 따른 Paging (Loader vs useEffect) #54

Open cryingdryice opened 1 month ago

cryingdryice commented 1 month ago

상황에 따른 Paging (Loader vs useEffect)

현재 YUQuiz에는 다양한 페이지와 페이징을 필요로 하는 페이지가 존재한다. 목록을 조회하는 페이지에서 페이징 기능을 요구하는데, 프론트에서 페이징을 구현하는 방식이 다양하게 존재함을 알게되었다.


페이징(Paging)이란?

페이징은 많은 양의 데이터를 페이지 단위로 나누어 화면에 표시하는 방식이다. 한 번에 모든 데이터를 로드하지 않고, 필요한 만큼만 로드하여 성능을 최적화 하고 사용자의 편의성을 높이는 데 주로 사용된다. image 위 사진과 같이 페이지 단위로 리스트를 조회할 수 있다.


URL 기반(Loader 사용) vs 상태 기반(useEffect 사용)

1. URL 기반 페이징 (Loader 사용)

URL 기반 페이징은 페이지 번호를 URL에 포함시키는 방식이다. 예를 들어, example.com/posts?page=2 또는 example.com/posts/2 같은 형식으로 페이지 번호를 쿼리 파라미터나 경로로 전달한다.

어떨때 사용할까?

사용자가 정보를 공유할 필요가 있는 페이지나, 오래 머물러 여러 상호작용 후에도 현재 페이지를 유지해야하는 경우 유용하다!

URL기반에도 두가지 방법으로 나뉘는 걸 발견했다. 쿼리 파라미터 방식 (/posts?page=2)과 경로 매개변수 방식 (/posts/2)이다.

1) 쿼리 파라미터 방식

여러 필터나 정렬 옵션 등을 함께 쿼리 파라미터로 전달할 수 있다. 예를 들어, ?keyword=example&categoryId=1&sortOption=DATE_DESC&page=2처럼 여러 상태를 동시에 관리할 수 있다.(페이지뿐만이 아닌 검색 키워드, 카테고리, 정렬 등도 링크공유 가능)

구현 방법

  1. React Router
const router = createBrowserRouter([
  {
    path: '/posts',  // 경로에 페이지 번호가 따로 포함되지 않음
    element: <PostListPage />,
    loader: postListLoader,  // loader에서 데이터를 로드
  },
]);
  1. Loader
export async function postListLoader({ request }) {
  const url = new URL(request.url);  // URL 객체 생성
  const page = url.searchParams.get('page') || 1;  // 쿼리 파라미터에서 추출
  ...
  const categoriesData = await getCategories();  // 카테고리
  const postsListData = await getPostsList("", null, "DATE_DESC", page);  // API 호출

  return {
    categories: categoriesData,
    postsList: postsListData.content,
    totalPages: postsListData.totalPages,
    currentPage: Number(page),
  };
}
  1. List 컴포넌트
const PostListPage = () => {
  const { categories, postsList, totalPages, currentPage } = useLoaderData();  // loader로부터 데이터 가져옴
  const [searchParams, setSearchParams] = useSearchParams();  // 쿼리 파라미터 조작
  const navigate = useNavigate();
  ...
  // 페이지 변경 핸들러
  const handlePageChange = (pageNumber) => {
    setSearchParams({ page: pageNumber });  // 쿼리 파라미터로 페이지 번호 설정
  };
  ...
}

2) 경로 매개변수 방식

쿼리 파라미터 방식에 비해 URL이 간결하다. 예를 들어, /posts/2처럼 경로에 페이지 번호만 담긴다. 검색 엔진이 선호하는 방식으로 SEO(검색 엔진 최적화)에 유리해 더 잘 노출된다.

구현 방법

  1. React Router
const router = createBrowserRouter([
  {
    path: '/posts/:page',  // 페이지 번호를 경로 매개변수로 처리
    element: <PostListPage />,
    loader: postListLoader,
  },
]);
  1. Loader
export async function postListLoader({ params }) {
  const page = params.page || 1;  // 경로 매개변수에서 페이지 번호 추출, 없으면 기본값 1
  ...
  const categoriesData = await getCategories(); // 카테고리
  const postsListData = await getPostsList("", null, "DATE_DESC", page);  // API 호출

  return {
    categories: categoriesData,
    postsList: postsListData.content,
    totalPages: postsListData.totalPages,
    currentPage: Number(page),
  };
}
  1. List 컴포넌트
const PostListPage = () => {
  const { categories, postsList, totalPages, currentPage } = useLoaderData();  // loader로부터 데이터 가져옴
  const navigate = useNavigate(); // URL경로 관리
  ...
  // 페이지 변경 핸들러
  const handlePageChange = (pageNumber) => {
    navigate(`/posts/${pageNumber}`);  // 경로 매개변수로 페이지 번호 변경
  };
  ...
} 

2. 상태 기반 페이징 (useEffect 사용)

상태 기반 페이징은 페이지 번호를 컴포넌트 상태에서 관리한다. 페이지 번호가 변경될 때마다 useEffect를 통해 데이터를 로드하는 방식이다. 이 방식은 URL과 무관하게 상태로만 페이지 번호를 관리한다.

어떨때 사용할까?

구현 방법

  1. List 컴포넌트
const PostListPage = () => {
  const [categories, setCategories] = useState([]);  // 카테고리 데이터
    ...
  const [currentPage, setCurrentPage] = useState(0);  // 현재 페이지 번호
  const [totalPages, setTotalPages] = useState(1);  // 전체 페이지 수

  // 데이터를 로드하는 useEffect
  useEffect(() => {
    const fetchData = async () => {
      try {
        // 카테고리 정보 로드
        const categoriesData = await getCategories();
        setCategories(categoriesData);

        // 게시글 리스트 로드
        const postsListData = await getPostsList(keyword, categoryId, sortOption, currentPage);
        setPostsList(postsListData.content);  // 게시글 목록 설정
        setTotalPages(postsListData.totalPages);  // 전체 페이지 수 설정
      } catch (error) {
        console.error('데이터를 불러오는 중 오류 발생:', error);
      }
    };

    // 페이지나 다른 조건이 변경될 때마다 데이터를 다시 로드
    fetchData();
  }, [keyword, categoryId, sortOption, currentPage]);  // 상태 변화에 따라 다시 로드

  // 페이지 변경 핸들러
  const handlePageChange = (newPage) => {
    setCurrentPage(newPage);
  };
  ...
}

제안

현재 프로젝트에는 페이징 구현을 우선으로 하여 상황에 맞지 않은 페이징 방법이 적용되어 있다고 생각했습니다. 퀴즈, 게시글 목록과 같이 사용자들이 서로 공유할 수 있는 페이지에는 Loader를 이용한 url 기반 페이징을, 마이페이지의 마이 리스트, 관리자 페이지의 목록 조회 같은 공유가 의미 없는 페이지에는 useEffect를 이용한 상태 기반 페이징을 적용함이 어떤가 싶습니다. 추후 리팩토링할 시 의견을 반영하겠습니다!

sernan96 commented 1 month ago

아주 좋은 글 아주 좋습니다. 저희는 사실 구현에 급급하기도 했고 또한 낯선 것이 대부분이었기 때문에 이렇게 새로운 것들을 알아가면서 이전의 것들을 바꿀 기회가 생기는 것이 힘도 들지만 한편으론 좋기도 하네요. ㅎㅎ 바뀌는 것을 반영할 일이 없거나 필요 없는 곳, 아닌 곳을 잘 분류해서 리팩토링 할 때 화이팅해봐요!!👍