과거 2022.4.22에 작성했던 글


string extends T를 추가로 검사하면 

string은 문자열 리터럴 타입을 상속하지 않으므로

string과 문자열 리터럴 타입을 구분할 수 있게 된다.

이것을 활용하여 다음과 같은 유틸리티를 만들었다.

import { generatePath } from 'react-router'
import type { ParamParseKey } from 'react-router/lib/router'
 
import type { IdentifiableString } from '~/types/utilityTypes'
 
// ParamParseKey는 :로 시작하는 동적 라우트 키 값의 이름을 뽑아온다.
// ParamParseKey<"/:userName/aaa/:blogId/edit"> -> 'userName' | 'blogId'
 
export const routeMatch = {
  login: '/login',
 
  categoryList: '/:userName/category',
  category: '/:userName/category/:categoryId',
  categoryPostList: '/:userName/category/:categoryId/list/:postId',
 
  newPost: '/:userName/post/new',
  editPost: '/:userName/post/:postId/edit',
} as const
 
type RouterPathFn<T extends string> = (
  ...params: IdentifiableString<ParamParseKey<T>> extends true
    ? [
        {
          [key in ParamParseKey<T>]: string
        }
      ]
    : []
) => string
 
const makePathFunc =
  <Path extends string>(patternPath: Path): RouterPathFn<Path> =>
  (...params) =>
    generatePath(patternPath, ...params)
 
type RouteMatch = typeof routeMatch
type PathNames = keyof RouteMatch
 
export const routerPaths = Object.fromEntries(
  Object.entries(routeMatch).map(
    ([pathName, path], cv) => [pathName, makePathFunc(path)]
  )
) as {
  [key in PathNames]: RouterPathFn<RouteMatch[key]>
}
const categoryListPath = routerPaths.categoryList({ userName: 'gururu' })
// => '/gururu/category'

이러면 routeMatch에 경로에 해당하는 문자열만 추가해주면 알아서 routerPaths에 해당하는 generator 함수가 만들어진다.