[리팩토링] 헤더 메뉴들의 onClick 함수 통합

팀 프로젝트 반응형 작업이 어느정도 되어서 진짜 코드 리팩토링을 해보고 싶다는 생각이 들었다. 그래서 가장 먼저 헤더의 메뉴들마다 바인딩 되어 있는 onClick 함수들을 살펴보았다.

변경 전 Container

  • 헤더의 메뉴들마다 각각 onClick 함수가 바인딩 되어 있고 각 메뉴 클릭 시 페이지 이동만 해주는 동일한 기능을 하고 있다.
  • 그래서 함수를 하나로 만들어서 클릭한 메뉴의 id값에 딸라 페이지를 이동하도록 변경할 예정이다.
  • 이렇게하면 Presenter로 내려주는 props의 개수도 좀 줄 줄어들 것 같다.
import { useRouter } from 'next/router';
import { useRecoilState } from 'recoil';
import { accessTokenState, userInfoState } from '../../../../commons/store';
import { useMutation, useQuery } from '@apollo/client';
import { FETCH_USER_LOGGED_IN, LOG_OUT } from '../../queries';
import React, { useEffect, useState } from 'react';
import LayoutHeaderUI from './header.presenter';

export default function HeaderContainer() {
  const router = useRouter();
  const [isToken, setIsToken] = useRecoilState(accessTokenState);
  const [userInfo, setUserInfo] = useRecoilState(userInfoState);
  const { data } = useQuery(FETCH_USER_LOGGED_IN);
  const [userLogOut] = useMutation(LOG_OUT);

  // 채팅방에서 헤더 비노출 처리를 위한 코드
  const HIDDEN_HEADER = ['/chat/[chatInfo]'];
  const isHiddenHeader = HIDDEN_HEADER.includes(router.pathname);
  const [currentPage, setCurrentPage] = useState({
    garden: false,
    community: false,
    chat: false,
    charge: false,
    profile: false,
  });

  useEffect(() => {
    setUserInfo(data?.fetchUser);
    setCurrentPage({
      garden: false,
      community: false,
      chat: false,
      charge: false,
      profile: false,
      [router.asPath.split('/')[1]]: true,
    });
  }, [data, router]);

  const onClickLogo = () => {
    router.push('/garden');
  };

  const onClickChat = () => {
    router.push('/chat');
  };

  const onClickGarden = () => {
    router.push('/garden');
  };

  const onClickCommunity = () => {
    router.push('/community');
  };

  const onClickSignIn = () => {
    router.push('/signin');
  };

  const onClickSignUp = () => {
    router.push('/signup');
  };

  const onClickCharge = () => {
    if (!isToken) {
      alert('Please Login');
      router.push('/signin');
    } else router.push('/charge');
  };

  const onClickLogOut = async () => {
    try {
      await userLogOut();
      setIsToken('');
      router.push('/garden');
    } catch (error) {
      if (error instanceof Error) alert(error.message);
    }
  };

  const onClickMoveMypage = () => {
    if (!isToken) {
      alert('Please Login');
      router.push('/signin');
    } else {
      router.push(`/profile/${userInfo.id}`);
    }
  };

  return (
    <LayoutHeaderUI
      isHiddenHeader={isHiddenHeader}
      onClickLogo={onClickLogo}
      onClickGarden={onClickGarden}
      onClickCommunity={onClickCommunity}
      onClickChat={onClickChat}
      onClickCharge={onClickCharge}
      onClickMoveMypage={onClickMoveMypage}
      isToken={isToken}
      data={data}
      onClickLogOut={onClickLogOut}
      onClickSignUp={onClickSignUp}
      onClickSignIn={onClickSignIn}
      currentPage={currentPage}
    />
  );
}

변경 후 Container

import { useRouter } from 'next/router';
import { useRecoilState } from 'recoil';
import { accessTokenState, userInfoState } from '../../../../commons/store';
import { useMutation, useQuery } from '@apollo/client';
import { FETCH_USER_LOGGED_IN, LOG_OUT } from '../../queries';
import React, { useEffect, useState } from 'react';
import LayoutHeaderUI from './header.presenter';

export default function HeaderContainer() {
  const router = useRouter();
  const [isToken, setIsToken] = useRecoilState(accessTokenState);
  const [userInfo, setUserInfo] = useRecoilState(userInfoState);
  const { data } = useQuery(FETCH_USER_LOGGED_IN);
  const [userLogOut] = useMutation(LOG_OUT);

  // 채팅방에서 헤더 비노출 처리를 위한 코드
  const HIDDEN_HEADER = ['/chat/[chatInfo]'];
  const isHiddenHeader = HIDDEN_HEADER.includes(router.pathname);
  const [currentPage, setCurrentPage] = useState({
    garden: false,
    community: false,
    chat: false,
    charge: false,
    profile: false,
  });

  useEffect(() => {
    setUserInfo(data?.fetchUser);
    // 현재 선택된 페이지에 맞는 헤더 메뉴 활성화를 위한 코드
    setCurrentPage({
      garden: false,
      community: false,
      chat: false,
      charge: false,
      profile: false,
      [router.asPath.split('/')[1]]: true,
    });
  }, [data, router]);

  const onClickRoute = (event: { currentTarget: { id: any } }) => {
    router.push(`/${event.currentTarget.id}`);
  };

  const onClickLogOut = async () => {
    try {
      await userLogOut();
      setIsToken('');
      router.push('/garden');
    } catch (error) {
      if (error instanceof Error) alert(error.message);
    }
  };

  const onClickMoveMypage = () => {
    if (!isToken) {
      alert('Please Login');
      router.push('/signin');
    } else {
      router.push(`/profile/${userInfo.id}`);
    }
  };

  return (
    <LayoutHeaderUI
      isHiddenHeader={isHiddenHeader}
      onClickMoveMypage={onClickMoveMypage}
      isToken={isToken}
      data={data}
      onClickLogOut={onClickLogOut}
      currentPage={currentPage}
      onClickRoute={onClickRoute}
    />
  );
}

코드의 길이도 많이 줄어들고 props도 줄어드니 한결 보기 좋아진 것 같다.

변경 전 Presenter

<Header.WrapperLogo
  id={'garden'}
  onClick={props.onClickLogo}
></Header.WrapperLogo>

변경 후 Presenter

<Header.WrapperLogo
  id={'garden'}
  onClick={props.onClickRoute}
></Header.WrapperLogo>
  • presenter는 코드 길이가 변하는건 없었다. 대신 로고, 메뉴1, 메뉴2등 메뉴별로 각각 다르게 바인딩 되어 있던 함수가 onClickRoute 함수 하나로 바인딩 되도록 변경되었다.

변경 전 Type 정의

export interface IHeaderUI {
  isHiddenHeader: boolean;
  onClickLogo: () => void;
  onClickGarden: () => void;
  onClickCommunity: () => void;
  onClickChat: () => void;
  onClickCharge: () => void;
  onClickMoveMypage: () => void;
  isToken: string;
  data: any;
  onClickLogOut: () => void;
  onClickSignUp: () => void;
  onClickSignIn: () => void;
  currentPage: any;
}

변경 후 Type 정의

typescript를 위한 타입 정의도 아래와 같이 많이 줄일 수 있었다.

export interface IHeaderUI {
  isHiddenHeader: boolean;
  onClickMoveMypage: () => void;
  isToken: string;
  data: any;
  onClickLogOut: () => void;
  currentPage: any;
  onClickRoute: (event: { currentTarget: { id: any } }) => void;
}