[리팩토링]_성능 최적화

hanseungjune·2023년 7월 30일

리팩토링

목록 보기
10/25
post-thumbnail

성능 최적화는 어떻게 진행되었는가?

애초에 three.js가 반영된 프로젝트라서 렌더링 측면에서 문제가 좀 있었다. 로그인을 하고나면, 다른 라우터로 이동하기가 안되었다. 하지만, 로그인 성공 후, 해당 조건으로 홈화면에서 window.reload()를 통해서 해결했지만, 성능이 문제였다.

그래서 모든 라우터에 참조되어있는 Nav2 컴포넌트를 성능 최적화 하기로 하였고, 본인이 맡은 MyPage가 성능 측면에서 상당히 좋지 않았기 때문에, 해당 페이지를 중심으로 성능 최적화를 진행하였다.

작성 코드 및 설명

MyPage.tsx

import { Link } from "react-router-dom";
import Buy from "../assets/Buy.png";
import inspector from "../assets/inspect.png";
import Insurance from "../assets/Insurance.png";
import Community from "../assets/Gallery.png";
import MyCar from "../assets/MyCar.png";
import Repair from "../assets/repairimg.png";
import ChangeKey from "../assets/ChangeKey.png";
import Sell from "../assets/Sell.png";
import Nav2 from "../components/Nav2";
import { TokenStorage } from "./../hooks/TokenStorage";
import {
  StyleMyPageContainer,
  StyleMyPageDiv,
  StyleMypageCardImg,
  StyleMypageCards,
} from "../style/mypage/MyPageStyle";
import { memo } from "react";
const tokenStorage = new TokenStorage();
const localStorageData = tokenStorage.getToken();
const cardInfo = [
  {
    path: "/user/mypage/mycarinfo",
    imgSrc: MyCar,
    alt: "MyCarInfo",
    text: "내 차 정보",
  },
  {
    path: "/user/mypage/repair",
    imgSrc: Repair,
    alt: "RepairInspector",
    text: "정비 내역",
  },
  {
    path: "/user/mypage/inspector",
    imgSrc: inspector,
    alt: "inspector",
    text: "검수 내역",
  },
  {
    path: "/user/mypage/community",
    imgSrc: Community,
    alt: "Community",
    text: "내가 쓴 글",
  },
  {
    path: "/user/mypage/buycontent",
    imgSrc: Buy,
    alt: "Buy",
    text: "구매 목록",
  },
  {
    path: "/user/mypage/sellcontent",
    imgSrc: Sell,
    alt: "Sell",
    text: "판매 목록",
  },
  {
    path: "/user/mypage/insurance",
    imgSrc: Insurance,
    alt: "Damage",
    text: "손상 내역",
  },
  {
    path:
      localStorageData?.userType === 0
        ? "/user/mypage/userpasswordmodify"
        : "/user/mypage/companypasswordmodify",
    imgSrc: ChangeKey,
    alt: "ChangeKey",
    text:
      localStorageData?.userType === 0
        ? "비밀번호 변경(유저)"
        : "비밀번호 변경(기업)",
  },
];

export interface CardType {
  card: {
    path: string;
    imgSrc: any;
    alt: string;
    text: string;
  };
}

const Card = memo(({ card }: CardType) => (
  <StyleMypageCards>
    <Link to={card.path}>
      <StyleMypageCardImg>
        <img src={card.imgSrc} alt={card.alt} />
      </StyleMypageCardImg>
      <p>{card.text}</p>
    </Link>
  </StyleMypageCards>
));

const MyPage = () => {
  return (
    <div>
      <Nav2 />
      <StyleMyPageContainer>
        <StyleMyPageDiv>
          {cardInfo.map((card, index) => (
            <Card key={index} card={card} />
          ))}
        </StyleMyPageDiv>
      </StyleMyPageContainer>
    </div>
  );
};

export default memo(MyPage);

MyPage 컴포넌트는 사용자의 마이 페이지를 표시하는 React 컴포넌트입니다. 각각의 카드를 클릭하면 해당 주제에 대한 페이지로 이동하게 됩니다.

  • CardType: 카드의 타입을 지정하는 인터페이스입니다. 카드는 경로, 이미지 소스, 대체 텍스트, 텍스트를 가지게 됩니다.

  • Card: 각 카드를 렌더링하는 컴포넌트입니다. 이 컴포넌트는 memo를 사용하여 성능을 최적화하였습니다. 이 컴포넌트는 CardType을 prop으로 받아 사용합니다.

  • MyPage: 전체 마이페이지를 렌더링하는 컴포넌트입니다. 각 카드를 map을 이용하여 반복적으로 렌더링합니다. 이 컴포넌트 또한 memo를 사용하여 성능을 최적화하였습니다.

/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import carBackground from "../assets/carBackground2.jpg";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import Logo from "../assets/Logo.png";
import { LOGOUT_REQUEST } from "../modules/LoginSubmitGlobal";
import { container } from "../style/mypage/MyPageStyle";
import { useMemo, useEffect, useState, useCallback } from "react";
import { TokenStorage } from "../hooks/TokenStorage";

const PAGE_TITLES: { [key: string]: string } = {
  "/": "Car-Born Home",
  "/login": "Login",
  "/myvehicle/registration": "Car Registration",
  "/user/mypage": "MyPage",
  "/user/mypage/mycarinfo": "MyCarInfo",
  "/user/mypage/repair": "MyRepairHistory",
  "/user/mypage/buycontent": "MyPurchaseHistory",
  "/user/mypage/sellcontent": "MySalesHistory",
  "/user/mypage/inspector": "MyInspectorHistory",
  "/user/mypage/insurance": "MyInsuranceHistory",
  "/getagreement": "TermsofUse",
  "/signup": "SignUp",
  "/searchid": "SearchID",
  "/searchid/searchidcomplete": "SearchID Complete",
  "/passwordresetcheck": "SearchPassword",
  "/passwordresetcheck/passwordreset": "ResetPassword",
  "/passwordresetcheck/passwordreset/passwordcomplete": "Reset Complete",
  "/user/mypage/community": "MyPostsHistory",
  "/user/mypage/userpasswordmodify": "ResetPassword",
  "/user/community": "Community",
  "/user/car/list": "CarList",
  "/user/car/sale": "CarSaleRegister",
  "/user/car": "MyCarRegister",
  "/user/community/write": "NewArticleWrite",
  "/user/self-repair": "CheckList",
};

const DYNAMIC_PAGE_TITLES = [
  { path: "/user/mypage/mycarinfo/:carId/detail", title: "MyCarInfo" },
  {
    path: "/user/mypage/repair/:resultId/completedetail",
    title: "MyRepairDetail",
  },
  {
    path: "/user/mypage/repair/:bookId/bookdetail",
    title: "MyRepairReserve",
  },
  {
    path: "/user/mypage/inspector/:resultId/completedetail",
    title: "MyInspectorDetail",
  },
  {
    path: "/user/mypage/inspector/:bookId/bookdetail",
    title: "MyInspectorReserve",
  },
  {
    path: "/user/mypage/insurance/:resultId/completedetail",
    title: "MyInsuranceDetail",
  },
  { path: "/user/car/:carId/:id", title: "DetailCarInfo" },
  { path: "/user/mypage/mycarinfo/:carId/detail", title: "CarDetail" },
];

export default function Nav2(msg: any) {
  const [title, setTitle] = useState("");
  const navigate = useNavigate();
  const location = useLocation();
  const dispatch = useDispatch();
  const isHome = location.pathname === "/";
  const isMap = location.pathname === "/user/map";

  const section2 = css`
    width: ${isHome || isMap ? "100%" : "80%"};
    height: 45vh;
    background-size: cover;
    background-repeat: no-repeat;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    background-color: rgba(0, 0, 0, 0.8);
    background-image: ${!isHome && !isMap ? `url(${carBackground})` : ""};
    position: relative;
  `;

  useEffect(() => {
    let title = PAGE_TITLES[location.pathname] || null;

    if (!title) {
      for (const { path, title: pageTitle } of DYNAMIC_PAGE_TITLES) {
        const regex = new RegExp(
          `^${path.replace(/:[^\s/]+/g, "([\\w-]+)")}$`,
          "g"
        );
        const match = location.pathname.match(regex);
        if (match) {
          title = pageTitle;
          break;
        }
      }
    }

    setTitle(title || "Unknown Page");
  }, [location.pathname, setTitle]);

  // 리팩토링
  const tokenStorage = useMemo(() => new TokenStorage(), []);
  const localStorageData = useMemo(() => tokenStorage.getToken(), [tokenStorage]);
  const [token, setToken] = useState(localStorageData?.accessToken);
  const LogoutSubmit = useCallback(async () => {
    dispatch({ type: LOGOUT_REQUEST });
    setToken(null);
  }, [dispatch]);

  const navigateToLogin = useCallback(() => {
    navigate("/login");
  }, [navigate]);

  const navigateToHome = useCallback(() => {
    navigate("/");
  }, [navigate]);

  const navigateToCarList = useCallback(() => {
    navigate("/user/car/list");
  }, [navigate]);

  const navigateToMap = useCallback(() => {
    navigate("/user/map");
  }, [navigate]);

  const navigateToCommunity = useCallback(() => {
    navigate("/user/community");
  }, [navigate]);

  const navigateToCar = useCallback(() => {
    navigate("/user/car");
  }, [navigate]);

  const navigateToSelfRepair = useCallback(() => {
    navigate("/user/self-repair");
  }, [navigate]);

  const navigateToMyPage = useCallback(() => {
    navigate(`/user/mypage`);
  }, [navigate]);

  useEffect(() => {
    let title = PAGE_TITLES[location.pathname] || null;

    if (!title) {
      for (const { path, title: pageTitle } of DYNAMIC_PAGE_TITLES) {
        const regex = new RegExp(
          `^${path.replace(/:[^\s/]+/g, "([\\w-]+)")}$`,
          "g"
        );
        const match = location.pathname.match(regex);
        if (match) {
          title = pageTitle;
          break;
        }
      }
    }

    setTitle(title || "Unknown Page");
  }, [location.pathname]);

  return (
    <div css={container}>
      <div className="section1">
        <div className="loginInfo">
          {token ? (
            <div
              className="logo"
              onClick={LogoutSubmit}
              css={{ cursor: "pointer" }}
            >
              LOGOUT
            </div>
          ) : (
            <div
              className="logo"
              onClick={navigateToLogin}
              css={{ cursor: "pointer" }}
            >
              LOGIN
            </div>
          )}
        </div>
      </div>
      <div css={section2} className="navSection2">
        <div className="menuBar">
          <div className="logo" onClick={navigateToHome}>
            <img
              src={Logo}
              alt="logo"
              width="32%"
              height="auto"
              className="logoImg"
            />
          </div>
          <div className="menu">
            <div className="item" onClick={navigateToCarList}>
              거래
            </div>
            <div className="item" onClick={navigateToMap}>
              예약
            </div>
            <div className="item" onClick={navigateToCommunity}>
              커뮤니티
            </div>
            <div className="item" onClick={navigateToCar}>
              내 차 등록
            </div>
            <div className="item" onClick={navigateToSelfRepair}>
              셀프 정비
            </div>
            {token ? (
              <div className="item" onClick={navigateToMyPage}>
                마이페이지
              </div>
            ) : null}
          </div>
        </div>
        <div className="location" css={{ cursor: "default" }}>
          {title}
        </div>
      </div>
    </div>
  );
}

Nav2 컴포넌트는 웹 페이지의 상단 네비게이션 바를 표시하는 React 컴포넌트입니다.

  • PAGE_TITLES: 페이지 경로와 그에 해당하는 페이지 제목을 매핑한 객체입니다.

  • DYNAMIC_PAGE_TITLES: 동적 라우팅을 사용하는 페이지의 경로와 그에 해당하는 페이지 제목을 담은 배열입니다.

  • title: 현재 페이지의 제목을 상태로 가지고 있습니다. 이 제목은 페이지 경로가 바뀔 때마다 업데이트됩니다.

  • useEffect: 페이지 경로가 바뀔 때마다 실행되는 useEffect 훅입니다. 이 훅은 페이지 경로에 따른 제목을 PAGE_TITLESDYNAMIC_PAGE_TITLES에서 찾아 title 상태를 업데이트합니다.

  • tokenStorage, localStorageData, token, LogoutSubmit: 사용자의 로그인 상태를 관리하는 데 사용되는 토큰 관련 변수와 함수입니다.

  • navigateToLogin, navigateToHome, navigateToCarList, navigateToMap, navigateToCommunity, navigateToCar, navigateToSelfRepair, navigateToMyPage: 각각의 버튼 클릭 시 이동할 페이지를 지정하는 함수입니다.

  • return: 네비게이션 바를 렌더링하는 부분입니다. 각 버튼의 클릭 이벤트에 위에서 정의한 함수들을 연결하였습니다. 또한, 사용자가 로그인한 상태인지에 따라 로그인/로그아웃 버튼을 다르게 표시합니다.

성능 최적화 측면에서 보면, 이 컴포넌트는 특정 이벤트나 상태 변경시에만 렌더링이 재실행되도록 useMemo, useCallback, useEffect 등을 적절히 사용하여 최적화하였습니다.

profile
필요하다면 공부하는 개발자, 한승준

0개의 댓글