Jwong.log
<더 좋은 타입스크립트 프로그래머로 만드는 11가지 팁> 아티클 체화하기 본문

지난 4월에 읽은 아티클인 <더 좋은 타입스크립트 프로그래머로 만드는 11가지 팁>의 내용을 체화하기 위해 노력한 경험들을 기록하려 한다.
아티클을 단순히 눈으로 읽기만 하지 않고 능동적으로 이해하고, 이후 아티클에 소개된 기법을 프로젝트에 직접 적용해보며 내 것으로 만들었다.
이를 통해 아티클의 제목처럼 과연 '더 좋은 타입스크립트 프로그래머'가 되었을지를 관전 포인트로 읽어봐주면 좋을 것 같다!
1. 능동적으로 이해하기
1.1. 알고 있던 지식과 엮어서 이해하기 (feat. 잉여 속성 체크)
해당 아티클의 #1 {집합(Set)}이라고 생각하기 챕터에 다음과 같은 구절이 있다.
type Measure = { radius: number }; (중략) Measure 타입은 radius이라는 숫자 필드를 포함하는 모든 객체의 집합입니다.
타입스크립트 초심자들은 일반적으로 Measure 타입의 객체에 radius 외에 다른 필드를 추가하면 타입 에러가 날 것을 예상한다.
하지만 여기서는 'radius를 포함하는' 모든 객체의 집합을 Measure 타입이라고 말한다.
마치 Measure 타입은 객체에 radius 필드가 있기만 하면 되고, 다른 필드가 추가돼도 상관 없는 것처럼 말이다.
그렇다면 Measure 타입의 객체에 radius 외에 다른 필드를 추가하면, 에러가 난다 vs 에러가 나지 않는다 무엇이 맞을까?
정답은 둘 다 그럴 수도 있고, 아닐 수도 있다.
상황에 따라 에러가 날 때도 있고 나지 않을 때도 있는 것이다.
이는 잉여 속성 체크를 하는 경우가 있고, 아닌 경우가 있기 때문이다.
따라서 자세한 원리가 궁금한 사람은 이에 대해 알아 보면 된다.
이렇게 알고 있던 지식과 연관지어 이해함으로써 타입스크립트의 집합 관점 개념을 더욱 단단하게 다질 수 있었다.
1.2. 궁금증 파고들기
크고 작은 궁금증을 모두 해소하기 위해 노력했다.
1.2.1. 객체 타입을 유니온 연산자로 묶으면 더 큰 집합일까?
#1 {집합(Set)}이라고 생각하기 챕터에 다음과 같은 문장이 있다.
| 연산자는 더 큰 집합을 만들지만 잠재적으로 공통적으로 사용 가능한 필드가 더 적습니다. (두 객체 타입이 합쳐져 있다면).
이 문장을 이해하기 어려웠는데, 스터디원의 도움을 받아 궁금증을 해소했다.
(해당 아티클은 FE 아티클 읽기 스터디에서 스터디원들과 함께 읽었다. to 재훈 고마워!)
집합 관점에서 유니온 연산자는 더 큰 집합을 만드는 것이 맞다.
그런데 다음 예제를 살펴보자.

결국 이렇게 유니온 연산자로 묶인 여러 객체 타입 중 어떤 타입인지 정확히 추론되기 전까지는 공통된 필드 밖에 사용할 수 없게 된다.
따라서 유니온 객체 타입은 때에 따라 각각의 객체 타입보다 더 작은 집합이 된다고 개념적으로 말할 수 있다.
1.2.2. 유니온 타입 분배 제어를 위한 [](대괄호 기호)는 배열과 관련이 있을까?
#5 유니온 타입 분배 제어 챕터에서는 다음 예제를 통해 []를 통해 유니온 타입 분배를 제어할 수 있다는 것을 보여준다.
type ToArray<T> = [T] extends [Array<unknown>] ? T : T[];
type Foo = ToArray<string | number>;
Foo는 (string | number)[] 타입으로 추론된다.
이렇게 []로 감싸진 유니온 타입은 괄호로 묶어져 취급된다.
이는 처음 보는 기법인데, 마침 예제가 배열과 관련된 데에다 쓰이는 기호가 []이다보니 혹시 배열에 의존성이 있는 문법이 아닐까? 하는 작은 의심이 들었다.
아마 아니겠지만, 확실히 하고 싶은 마음에 아래 예제를 작성하여 확인해보았다.
type ToPromise1<T> = [T] extends [Promise<unknown>] ? T : Promise<T>;
// Promise<string | number> 타입이 됨.
type Foo1 = ToPromise1<string | number>;
type ToPromise2<T> = T extends Promise<unknown> ? T : Promise<T>;
// Promise<string> | Promise<number> 타입이 됨.
type Foo2 = ToPromise2<string | number>;
역시 배열과 관련이 있는 문법은 아니었다.
1.2.3. 철저한 검사를 통한 컴파일시 처리되지 않은 케이스 체크의 원리는 무엇일까?
#6 철저한 검사를 통한 컴파일시 처리되지 않은 케이스 체크 챕터의 내용만으로는 어떻게 철저한 검사가 이루어진다는 것인지 단번에 와닿지가 않았다.
이는 이 스택오버플로우 질문의 첫 번째 답변을 통해 이해할 수 있었다.
아주 친절한 예제를 작성해놓았다.

1.2.4. Mapped Type?
type ContentTypes = 'news' | 'blog' | 'video';
// 메서드 목록이 추출된 제네릭 factory 타입입니다.
// 주어진 Config의 모양을 기반으로 합니다.
type ContentFactory<Config extends Record<ContentTypes, boolean>> = {
[k in string & keyof Config as Config[k] extends true ? `create${Capitalize<k>}` : never]: () => Content;
};
// 사용할 수 있는 콘텐츠의 타입을 표시하기 위한 config입니다.
const config = { news: true, blog: true, video: false }
satisfies Record<ContentTypes, boolean>;
type Factory = ContentFactory<typeof config>;
// Factory: {
// createNews: () => Content;
// createBlog: () => Content;
// }
Mapped Type이나 복잡한 타입스크립트를 통해 유틸리티 타입을 만드는 것이 익숙하지 않다면 이 예제는 굉장히 어렵게 다가올 수 있다.
하지만 알고 보니 타입스크립트 공식 문서의 Mapped Types 챕터의 소개된 예제들을 잘 짬뽕한 것이기에 해당 문서만 잘 읽는다면 수월하게 이해할 수 있다.
Mapped Type의 기본 형태를 이해한 다음, as를 통해 key를 Remapping 하는 기법을 이해하면 된다.
특히 never 타입을 통해 조건에 맞지 않는 키를 필터링 할 수 있다는 것을 이해하자.
You can filter out keys by producing never via a conditional type:
1.3. 아티클 저자의 의견과 다르게 생각해보기
비록 이 글을 작성하기 위해 재고해보니 당시에 예제를 잘못 이해하고 냈던 의견이었지만, 내용을 마냥 받아들이지 않고 다르게도 생각해보기 위해 노력했다.

2. 직접 사용해보기
백문이 불여일견이라고, 개발 지식은 직접 사용해보지 않으면 쉽게 잊혀진다.
타입이 집합이라는 관점과 Mapped Type은 한 번씩 공부한 적이 있는데도 불구하고 이해하는 데에 시간을 들였던 것을 보면 정말 그렇다.
그렇기에 아티클을 읽은지 얼마 안된 시점에, 하고 있던 프로젝트에서 옵셔널 필드 대신에 구분된 유니온 사용이 적절한 경우를 생각해내 바로 적용해보았다.
https://github.com/sopt-makers/sopt-playground-frontend/pull/668
refactor: Career 타입 구별된 유니온으로 정확한 타이핑 할 수 있도록 하기 by NamJwong · Pull Request #668
🤫 쉿, 나한테만 말해줘요. 이슈넘버 close #658 🧐 어떤 것을 변경했어요~? Career 타입의 endDate 필드가 string | null 이었는데, 이는 boolean 타입의 isCurrent 필드에 의해 정해지는 것이었는데, isCurrent - t
github.com
또한, 철저한 검사를 통한 컴파일시 처리되지 않은 케이스 체크가 적절한 작업이 있어 적용해보았다.
기존에도 자주 사용하던 기법인 타입 단언을 피하기 위한 타입 명제 사용도 함께 적용했다.
https://github.com/sopt-makers/sopt-playground-frontend/pull/669
fix: 프로필 상세 > 프로젝트 뱃지에 프로젝트 카테고리 모두 표시되도록 by NamJwong · Pull Request #669
🤫 쉿, 나한테만 말해줘요. 이슈넘버 close #664 🧐 어떤 것을 변경했어요~? 기존 코드가 솝커톤, 앱잼 외의 카테고리는 표시가 안되도록 돼있어서 모든 카테고리를 반영하도록 수정했습니다! 🤔
github.com
이후, Mapped Type과 satisfies를 통해 효과적으로 하드 데이터를 관리하기도 했다.
https://github.com/sopt-makers/sopt-playground-frontend/pull/727
feat: 멘토링 데이터 하드 코딩 및 프로바이더 만들기 by NamJwong · Pull Request #727 · sopt-makers/sopt-playg
🤫 쉿, 나한테만 말해줘요. 이슈넘버 close # 🧐 어떤 것을 변경했어요~? 멘토링 데이터 하드 코딩 멘토링 데이터 프로바이더 만들기 🤔 그렇다면, 어떻게 구현했어요~? #720 에서 했던 작업을 기
github.com