[Codecamp-Week5] React HOC vs HOF (with 권한 분기)

·2022년 8월 7일
0
post-custom-banner

HOC, HOF 개념은 이전 글에서 공유한 Closure와 관련있다.
Closure에 대해 모른다면 이전 글부터 숙지하기!

1. 권한 분기

HOC (Higher Order Component)는 보통 권한 분기를 위해 많이 활용되므로, 권한 분기에 대해 먼저 알아보자!
권한 분기란 한글 그대로 권한을 나눈다는 의미이다.
마이페이지 메뉴는 로그인을 한 user만 접근이 가능해야 한다.
더 넓게 볼 경우 user page와 admin page가 나뉘어져 있는 상황에서 user는 admin page에 접근 할 수 없도록 설정해야 할 것이다.
이러한 권한 분기를 위해서 HOC를 활용할 수 있다.

2. HOC

1) HOC란?

HOC란 Higher Order Component의 약자로 상위 컴포넌트를 말한다.
A 컴포넌트와 B 컴포넌트 사이에 C 컴포넌트를 넣고 싶다면 C 컴포넌트를 B 컴포넌트보다 먼저 시행되도록 설정해야 한다. 여기서 C 컴포넌트를 HOC라고 할 수 있다.

사실 HOC를 사용하지 않고도 권한 분기 설정이 가능하다.
로그인 관련 accessToken이 없는 경우 로그인 페이지로 돌려보내기 위해 로그인이 필요한 각 페이지마다 useEffect 구문을 넣어주면 된다.
그러나 이는 노가다 코딩에 불구할 뿐, 노가다 코딩을 통해 구현을 했다고 하더라도 수정이 필요한 경우 또 똑같은 노가다 과정을 겪어야 하는 문제가 있다.
이러한 문제를 해결하기 위해 HOC를 활용할 수 있다.

2) 권한분기에 HOC 활용하기

(1) HOC 원리


Aaa 컴포넌트와 Bbb 컴포넌트 사이에 먼저 실행돼야 할 권한 분기 컴포넌트를 넣어주고 싶다.
이를 위해선 Aaa 컴포넌트에 있는 qqq를 props를 통해 Bbb 컴포넌트까지 넘겨줘야 한다.
권한 분기 컴포넌트는 전체 컴포넌트에서 권한 분기가 필요한 컴포넌트에 적용을 할 수 있어야 하므로, _app.tsx 컴포넌트와 특정 컴포넌트 사이에 들어가 특정 컴포넌트보다 먼저 실행되면 될 것이다.

(2) HOC 구성


HOC를 더 간결하게 구성하기 위해서 화살표 함수를 활용할 수 있다.
HOC가 실행된 후 실행되어야 할 Component는 함수를 정의해준 후 HOC로 묶어주면 HOC 실행 후 해당 Component가 실행된다.

(3) HOC 살펴보기

_app.tsx

ApolloSetting 관련 부분은 따로 컴포넌트로 분리해뒀다!

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <RecoilRoot>
      <ApolloSetting>
        <Global styles={globalStyles}></Global>
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </ApolloSetting>
    </RecoilRoot>
  );
}

export default MyApp;

ApolloSetting Component

interface IApolloSettingProps {
  children: ReactNode;
}

const APOLLO_CACHE = new InMemoryCache();

export default function ApolloSetting(props: IApolloSettingProps) {
  const [accessToken, setAccessToken] = useRecoilState(accessTokenState);
  const [userInfo, setUserInfo] = useRecoilState(userInfoState);
  
   useEffect(() => {
      console.log("지금은 브라우저다!");
      const accessToken = localStorage.getItem("accessToken") || "";
      const userInfo = localStorage.getItem("userInfo");
      setAccessToken(accessToken);

      if (!accessToken || !userInfo) return;
      // string으로 변환해줬기 때문에 다시 객체로 변환해서 넣어주기
      setUserInfo(JSON.parse(userInfo));
    }, []);

  const uploadLink = createUploadLink({
    uri: "http://backend08.codebootcamp.co.kr/graphql",
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });

  const client = new ApolloClient({
    link: ApolloLink.from([uploadLink]),
    cache: APOLLO_CACHE,
    connectToDevTools: true,
  });

  return (
    <ApolloProvider client={client}>
        {props.children}
    </ApolloProvider>
  );
}

권한분기 HOC Component

export const withAuth = (Component) => (props) => {
  const router = useRouter();

  useEffect(() => {
    if (!localStorage.getItem("accessToken")) {
      alert("로그인 후 이용 가능!");
      router.push("/23-06-login-check-hoc");
    }
  }, []);

  return <Component {...props}></Component>;
};

로그인 완료 페이지 (=my page) Component

function LoginSuccessPage() {
  const [userInfo] = useRecoilState(userInfoState);

  return <div>{userInfo.name}님 환영합니다!</div>;
}

export default withAuth(LoginSuccessPage);

3. HOF

1) HOF란?

HOF도 HOC와 거의 비슷하다.
다만, HOC와의 차이점은 HOF는 return 부분에 JSX 요소가 없다.

기존까지는 특정 버튼 클릭 시 해당 id로 이동하기 위해 button에 id를 지정해주고 event.target.id를 활용하였다.
그러나 이 방법은 id가 중복되어 작성되는 경우 오작동의 여지가 있을 수 있다.
따라서 event.target.id 대신 HOF를 활용할 수 있다.

2) HOF 살펴보기

게시물 목록에서 특정 게시물 클릭 시 특정 게시물로 이동되도록 하는 상황을 살펴보자!

기존 방법

const FETCH_BOARDS = [
  { _id: "111", writer: "철수", title: "안녕하세요" },
  { _id: "222", writer: "영희", title: "안녕하세요" },
  { _id: "333", writer: "훈이", title: "안녕하세요" },
  { _id: "444", writer: "맹구", title: "안녕하세요" },
];

export default function HofPage() {
  const router = useRouter();


  const onClickMove = (event: MouseEvent<HTMLDivElement>) => {
      router.push(`/boards/${event.target.id}`);
    };

  return (
    <div>
      {FETCH_BOARDS.map((el) => (
        <div key={el._id} id={el._id} onClick={onClickMove}>
          <span>{el.writer}</span>
          <span>{el.title}</span>
        </div>
      ))}
    </div>
  );
}

HOF 방법

const FETCH_BOARDS = [
  { _id: "111", writer: "철수", title: "안녕하세요" },
  { _id: "222", writer: "영희", title: "안녕하세요" },
  { _id: "333", writer: "훈이", title: "안녕하세요" },
  { _id: "444", writer: "맹구", title: "안녕하세요" },
];

export default function HofPage() {
  const router = useRouter();


  const onClickMove = (boardId: string) => (event: MouseEvent<HTMLDivElement>) => {
      router.push(`/boards/${boardId}`);
    };

  return (
    <div>
      {FETCH_BOARDS.map((el) => (
        <div key={el._id} onClick={onClickMove(el._id)}>
          <span>{el.writer}</span>
          <span>{el.title}</span>
        </div>
      ))}
    </div>
  );
}
profile
개발을 개발새발 열심히➰🐶
post-custom-banner

0개의 댓글