고차 컴포넌트 조합으로 컴포넌트 관심사 분리하기

김형빈·2024년 10월 17일

TIL

목록 보기
9/13

디자인 패턴을 공부하며...

자바스크립트 + 리액트 디자인 패턴을 공부하며 리액트 디자인 패턴 중 고차 컴포넌트 조합을 사용해 로딩 컴포넌트를 분리하는 예시를 접하게 되었다. 이 패턴이 WorkConnect에서 react-query를 사용하면서 로딩 컴포넌트를 계속해서 반복해서 작성해야 했던 문제를 해결할 수 있는 좋은 방법이 될 거 같다는 생각이 들어 직접 적용해 보기로 하였다.

1. WithLoader 컴포넌트 생성

먼저, Loading과 Error컴포넌트를 호출하는 공통 컴포넌트로 WithLoader를 제작해보자

const WithLoaderError = ({ Element, isLoading, isError }: WithLoaderErrorProps) => {
  if (isLoading) return <Loading />;
  if (isError) return <NotFoundError />;

  return <Element />;
};
  • Loading 상태와 Error 상태를 매개변수로 받아서 Loading,Error, Element를 반환한다.

2. HomePage 컴포넌트 수정

이전

const Homepage = () => {
  return <HomeMain />
}
  • HomeMain 컴포넌트를 호출하는 역할

이후

const Homepage = () => {
  const workspaceId = useWorkspaceId();
  const workspaceUserId = useUserStore((state) => state.workspaceUserId);

  const { workspaceUser, isPending: userIsPending, isError: userIsError } = useWorkspaceUser(workspaceUserId);
  const {
    workspaceUserList,
    isPending: userListIsPending,
    isError: userListIsError
  } = useWorkspaceUserList(workspaceId, workspaceUserId);
  const isLoading = userIsPending || userListIsPending;
  const isError = userIsError || userListIsError;

  return (
    <WithLoaderError
      Element={() => (
        <HomeMain
          workspaceId={workspaceId}
          workspaceUserId={workspaceUserId}
          workspaceUser={workspaceUser}
          workspaceUserList={workspaceUserList}
        />
      )}
      isLoading={isLoading}
      isError={isError}
    />
  );
};
  • 페이지에 필요한 데이터를 호출하는 역할도 맡도록 설계하였다.
  • Homepage에서 react-qeury 커스텀 훅을 호출하고, 데이터와 loading, error 상태를 전달한다.

3. HomeMain 컴포넌트 수정

이전

const HomeMain = () => {
  const workspaceId = useWorkspaceId();
  const workspaceUserId = useUserStore((state) => state.workspaceUserId);

  const { workspaceUser, isPending: userIsPending, isError: userIsError } = useWorkspaceUser(workspaceUserId);
  const {
    workspaceUserList,
    isPending: userListIsPending,
    isError: userListIsError
  } = useWorkspaceUserList(workspaceId, workspaceUserId);

  if (userIsPending || userListIsPending) return <Loading />;

  if (!workspaceUser || userIsError || userListIsError) return <NotFoundError />;


  return (
    <div className="px-[16px] mt-[26px] lg:mt-[22px] lg:pl-[42px] lg:pr-[16px] lg:gap-[42px]">
      <HomeMemberCard workspaceId={workspaceId} workspaceUserId={workspaceUserId} workspaceUser={workspaceUser} />
      <MemberList workspaceUserList={workspaceUserList} />
    </div>
  );
};
  • HomeMain은 HomePage의 Main을 그리는 컴포넌트이지만 데이터 호출, 로딩 컴포넌트 호출, 에러 컴포넌트 호출 등 의도하지 않은 역할들도 맡게 되었다.

이후

const HomeMain = ({ workspaceId, workspaceUserId, workspaceUser, workspaceUserList }: HomeMainProps) => {
  return (
    <div className="px-[16px] mt-[26px] lg:mt-[22px] lg:pl-[42px] lg:pr-[16px] lg:gap-[42px]">
      <HomeMemberCard workspaceId={workspaceId} workspaceUserId={workspaceUserId} workspaceUser={workspaceUser} />
      <MemberList workspaceUserList={workspaceUserList} />
    </div>
  );
};
  • 데이터를 호출하는 코드와 로딩, 에러 컴포넌트를 모두 제거하고 뷰만 신경쓰는 컴포넌트로 분리하였다

4. Profile 페이지에서 재사용

 const Profile = () => {
   ...
  return (
    <WithLoaderError
      Element={() => (
        <ProfileMain
          workspaceUser={workspaceUser}
          workspaceId={workspaceId}
          targetWorkspaceUserId={targetWorkspaceUserId}
        />
      )}
      isLoading={isPending}
      isError={isError}
    />
  );
};
  • 프로필 페이지에서도 WithLoaderError 컴포넌트를 재사용하여 profileMain 컴포넌트의 로딩과 에러 컴포넌트도 분리하였다.

고차 컴포넌트의 단점

컴포넌트의 역할을 분리하는데 성공히였지만 고차 컴포넌트에도 단점이 존재한다.

기존 컴포넌트의 깊이

<HomePage>
  <HomeMain/>
</HomePage>

변경된 컴포넌트의 깊이

<HomePage>
  <WithLoaderError>
    <HomeMain/>
  </WithLoaderError>
</HomePage>
  • 현재는 하나의 컴포넌트를 추가하여 분리하여 크게 문제가 없지만 이렇게 분리해야하는 컴포넌트가 많아질수록 관
    리하기 힘들어질 수 있으므로 주의가 필요하다
profile
The secret of getting ahead is getting started.

0개의 댓글