본문 바로가기

Language/TypeScript

[TypeScript] 중첩된 객체의 값을 타입으로 만들고 싶을 때

 

 

 

 

중첩된 객체에서 값만 뽑아서 타입으로 만들 수 있을까?
🤔

 

 

 

 

 

 

const ENDPOINTS = {
  AUTH: {
    SIGN_UP: 'POST /auth/sign-up',
    SIGN_IN: 'POST /auth/sign-in'
  }
}

 

만약 이런 형태의 객체가 있다고 해봅시다.

 

이 객체의 값을 사용하려면 ENDPOINTS.AUTH.SIGN_UP 으로 접근할 수 있습니다.

 

제가 하고 싶은 것은 이렇게 중첩된 객체에서 value만 모아서 하나의 타입으로 사용하는 것입니다.

 

수동으로 type Endpoint = 'POST /auth/sign-up' | 'POST /auth/sign-in' | ... 로 선언할 수도 있겠지만,

 

이 객체의 몸집이 훨씬 더 크다고 할 때는 꽤나 귀찮고 더러운 코드가 될 것 같습니다.

 

 

 

이렇게 해주면 간단합니다.

const ENDPOINTS = {
  AUTH: {
    SIGN_UP: "POST /auth/sign-up",
    SIGN_IN: "POST /auth/sign-in"
  }
} as const;

type NestedValues<T> = T extends object ? NestedValues<T[keyof T]> : T;

type Endpoint = NestedValues<typeof ENDPOINTS>;

 

첫째, NestedValues<T>에서 <T>는 제네릭 타입을 의미합니다.

 

 

둘째, T extends object ? a : b 는 TypeScript의 조건부 타입(conditional types)으로,

 

extends의 왼쪽 타입을 extends의 오른쪽 타입에  할당할 수 있다면 a를, 그렇지 않다면 b를 얻습니다.

 

T가 객체라면 NestedValues<T[keyof T>, T가 객체가 아니라면 T가 타입이 됩니다.

 

 

셋째, keyof는 객체 타입 T의 key만 뽑아서 유니언 타입으로 만들어줍니다.

 

그럼 T[keyof T]는 객체의 값이 되겠지요.

 

결과적으로 type NestedValues<T> = T extends object ? NestedValues<T[keyof T]> : T 는

 

중첩 객체에서 값만 재귀적으로 추출하여 타입으로 만드는 커스텀 유틸리티 타입을 만드는 것입니다.

 

 

넷째, TypeScript에서의 typeof는 컴파일 타임에 변수나 프로퍼티의 타입을 추론할 수 있게 해줍니다.

(JS에서는 런타임에서 string으로 데이터 타입을 반환하는 용도로만 가능)

 

type Endpoints = NestedValues<typeof ENDPOINTS>;

 

이 구문에서는 ENDPOINTS가 객체이므로 객체의 타입 구조를 타입으로 만드는 것입니다.

 

typeof ENDPOINTS는 IDE에서 아래와 같은 객체 타입으로 추론해줍니다.

 

이렇게 끝내면 문제가 생깁니다. 

 

typeof 연산자로 객체의 원래 값의 타입에 따라 string으로 추론했기 때문에

 

NestedValues<typeof ENDPOINTS>의 결과는 string이 되어버리거든요.

 

 

다섯째, 원래 목표였던 객체의 리터럴 값을 타입으로 만드려면 객체 선언에 as const를 붙여야 합니다.

 

이를 const-assertion 이라고 합니다.

 

const ENDPOINTS = { ... } as const; 와 같이 const-assertion을 해주면,

 

각 프로퍼티가 읽기 전용(readonly)이 되면서 값의 리터럴 타입으로 유지할 수 있습니다.

 

 

결과적으로 Endpoint의 타입이 중첩 객체의 리터럴 값으로 구성된 유니언 타입이 된 것을 확인할 수 있었습니다.

 

 

결론

TypeScript의 제네릭, conditional types, typeof, keyof, const-assertion을 활용하여 중복 코드 없이 중첩된 객체의 값들을 타입으로 활용할 수 있습니다.

 

 

참고

Conditional Types

📘 객체를 타입으로 변환 - keyof / typeof 사용법

Keyof Type Operator

Typeof Type Operator

readonly and const