constant로 관리되는 DynamicRoute의 optional rendering 전략

pengooseDev·2023년 3월 29일
0
post-thumbnail

사건의 발단

줄줄이 소시지 코드에 눈이 아파온다.
하드코딩된 값이 많아지고 반복된 작업이 눈이 밟혀 리팩터링을 진행했다.


하드코딩 값 분리

우선, route와 관련된 값들을 하나의 객체로 빼내어, 상수로 관리하고자 하였다.

constant/routes

types/routes

route(key)값에 대한 value들을 하나하나 Object.freeze 할 경우, 소프트 코딩 및 TS의 장점을 살리지 못한다고 판단하여, RouteMap type을 선언하는 과정에서 Readonly를 RouteConfig(value)에 먹여줬다.


적용!

before

after

굳굳! 깔끔해졌다!


또 다른 문제점 발생

현재 ROUTE_MAP의 HAS_NAV field의 값에 따라, Nav 컴포넌트를 optional하게 rendering시킬 계획이었다.

허나, 한 가지 문제점을 직면하게 되었다.

동적라우팅을 사용하는 경우 useLocation의 결과 값은 '/task/1'이기 때문에 '/task/:id'와 값이 다르다는 문제가 발생했다.

어떻게 넘길 것인가!
useRoutes, useMatch 등등 여러 해결책을 시도해보았지만 생각보다 해당 issue를 해결하는 것이 쉽지 않았다.

오후 11시경 시작한 고민을 리팩터링 포함 오전 3시 쯤에서야 끝마쳤다.. 물론, 하드코딩된 값을 사용하면 된다는 간단한 해결책이 있지만, 공부를 하는 과정이기 때문에 문제 해결에 집중을 하고자 하였고 충분히 고민해볼 가치가 있는 문제점이라 생각하였다.

정규표현식

결국, 어렵지만 근본적인 해결책인 정규표현식을 채택하였다.
ROUTE_MAP 상수의 key값에서 동적 라우팅된 부분(:id)를 실제 useLocation의 id의 type으로 변환시켜 match가 되는 값이 있는지 확인하면 해결될 문제였다.

function App() {
  const { pathname } = useLocation();
  const navArr = Object.entries(ROUTE_MAP)
    .filter(([, value]) => value.HAS_NAV)
    .map(([path]) => path);

  const hasNav = navArr.some((path: string) => {
    const pathRegex = new RegExp(`^${path.replace(/:\w+/g, '\\d+')}$`);

    return pathRegex.test(pathname);
  });

  return (
    <>
      {hasNav && <Nav />}
      <ContentWrapper>
        <Router />
      </ContentWrapper>
    </>
  );
}

key값의 route에 동적라우팅(/:id)부분을 실제 id가 들어올 수 있는 값으로 변경해주었다.

new RegExp(^${path.replace(/:\w+/g, '\\d+')}$)

replace의 결과가 되는 부분에서 w를 사용해 string도 받는 것이 일반적이겠지만, BE에서 task나 project가 Create될 때, id를 autoIncrease를 관리하고 있기 때문에 number만 받는 것이 안전하다고 판단했다.


추가 리팩터링(모듈화)

해당 로직은 두 군데에서 사용된다.

  1. Nav 컴포넌트 렌더링 여부를 결정하는 App 컴포넌트
  2. Nav 컴포넌트 유무에 따라 자동적으로 Content가 들어가는 컴포넌트의 크기와 위치를 결정하는 ContentWrapper 컴포넌트.

두 군데에서 반복사용되는 로직이기 때문에 함수 형태로 빼내주었다.


적용

굳 👍

0개의 댓글