안녕하세요. 단단입니다.
제가 올해 1월 중순부터 FE 개발자들과 '타입챌린지' 스터디를 하고 있습니다.
타입챌린지는 유틸리티 타입 등을 다양한 방식으로 구현하면서 타입 시스템 작동 원리를 이해하고, 문제를 해결하는 챌린지입니다.
최근 리액트 코어를 타입스크립트로 구현하면서 제네릭을 유연하게 사용하고 싶다는 생각이 강하게 들었습니다. 전에 멘토님 2명이 타입챌린지를 해봤을 때 타입을 더 잘 쓰는데 도움됐다고 추천하셔서 타입챌린지 스터디원을 모집해 시작했습니다.
팀원들과 매일 한 문제씩 풀다보니 EASY 레벨 13문제를 다 풀었더라고요!
그래서 (체감 상 EASY하지 않은) EASY 레벨 문제 풀이와 함께 배운 점을 정리해보려고 합니다.
'타입스크립트를 왜 사용하는지'는 면접 단골 질문이기도 합니다.
타입스크립트는 변수, 함수, 객체의 타입을 미리 선언하면서 컴파일 시점에 타입 오류를 예측할 수 있고, 런타임 오류를 줄여 코드 안정성을 높입니다.
실제로 개발을 하면서 null 처리를 제대로 안 했을때 렌더링 자체에 문제가 생겼고, 이를 경험하면서 예측가능한 코드를 쓰는 게 중요하다고 체감했습니다.
또, 타입을 잘 쓰면 코드 편집기에서 자동완성 기능 등을 활용할 수 있기 때문에 좀 더 편리하게 개발을 할 수 있습니다. 즉, 개발자의 생산성을 높이는 데도 도움이 됩니다.
그래서 타입을 잘쓰고 싶다고 생각했고, 타입챌린지를 하고 있습니다.
T
에서 K
프로퍼티만 선택해 새로운 오브젝트 타입을 만드는 내장 제네릭 Pick<T, K>
을 이를 사용하지 않고 구현하세요.type MyPick<T, K extends keyof T> = { [key in K]: T[key] }
T
의 모든 프로퍼티를 읽기 전용(재할당 불가)으로 바꾸는 내장 제네릭 Readonly<T>
를 이를 사용하지 않고 구현하세요.type MyReadonly<T> = { readonly [Key in keyof T] : T[Key] }
type TupleToObject<T extends readonly any[]> = { [P in T[number]]: P }
type First<T extends any[]> = T extends [] ? never : T[0]
Length<T>
를 구현하세요.type Length<T extends readonly any[]> = T['length']
type MyExclude<T, U> = T extends U ? never : T
예시: Promise<ExampleType>
이 있을 때, ExampleType
을 어떻게 얻을 수 있을까요?
type MyAwaited<T extends PromiseLike<any>> = T extends PromiseLike<infer U>
? U extends PromiseLike<any>
? MyAwaited<U>
: U
: never;
C
, 참일 때 반환하는 타입 T
, 거짓일 때 반환하는 타입 F
를 받는 타입 If
를 구현하세요. C
는 true
또는 false
이고, T
와 F
는 아무 타입입니다.type If<C extends boolean, T, F> = C extends true ? T : F
type Concat<T extends readonly any[], U extends readonly any[]> = [...T, ...U];
type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] ? U extends T ? Equal<First, U> extends true ? true : Includes<Rest, U> : false
type Push<T extends any[], U> = [...T, U]
Array.unshift
의 타입 버전을 구현하세요.type Unshift<T extends any[], U> = [U, ...T]
type Concat<T extends any[], U extends any[]> = [P in keyof T | P in keyof U]
Parameters<T>
를 이를 사용하지 않고 구현하세요.type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer S) => any ? S : never
저는 혼자 공부할 때 보다 스터디를 통해 함께 할 때 의지가 더 생깁니다. 그래서 공부하는 패턴과 환경을 만들기 위해 일부러 여러 개의 스터디를 진행합니다.
이번엔 같은 코드를 구현해도 사람마다 다른 질문과 호기심을 가졌다는 점에서 확실히 혼자 공부할 때보다 더 많은 것들을 배웠습니다.
한 예시로, 팀원이 "배열에 readonly를 사용하면 튜플이 되는 게 아닌가"라는 의문을 가졌습니다.
찾아보니 readonly는 컴파일 시점에만 작동하는데, 길이를 고정하지 않고, 요소 타입을 고정하지 않고, 새로운 배열을 할당할 수 있기 때문에 readonly 배열이 자동으로 튜플이 되진 않는다는 것을 알았습니다.
즉, readonly 배열은 튜플과 달리 길이가 고정되지 않으므로 같은 타입의 요소가 몇 개 들어가든 타입 체킹에서 허용됩니다.
역시 함께 하니 공부하는 즐거움이 더 커진 스터디였습니다! 다음 레벨 문제 풀이도 화이팅입니다!
모든 피드백을 환영합니다. 읽어주셔서 감사합니다.