HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
👻
개발 기록
/
🐧
이펙티브 타입스크립트
/
📗
4장 타입 설계
📗

4장 타입 설계

item 28

  • 유효한 상태만 표현하는 타입을 지정하자.
  • 유효한 상태를 표현하는 값만 허용한다면 코드를 작성하기 쉬워지고 타입 체크가 용이해짐.
interface State { pageText: string; isLoading: boolean; error?: string; } // 아래와 같이 바꿀 수 있음. interface RequestPending { state: 'pending'; } interface RequestError { state: 'error'; error: string; } interface RequestSuccess { state: 'ok'; pageText: string; } type RequestState = RequestPending | RequestError | RequestSuccess; interface State { currentPage: string; requests: {[page: string]: RequestState}; }
 

item 29

  • 매개 변수 타입은 느슨하게, 반환 타입은 엄격하게 하자.
interface CameraOptions { center?: LngLat; zoom?: number; bearing?: number; pitch?: number; } // 아래와 같이 바꿀 수 있음. interface Camera { center: LngLat; zoom: number; bearing: number; pitch: number; } interface CameraOptions extends Omit<Partial<Camera>, 'center'> { center?: LngLatLike; }
 

item 30

  • 주석, 변수명에 타입을 적지말자.
    • ageNum보다 age:number를 사용하듯이.
    • 코드는 바꾼 채 주석을 안바꾸는 등 타입 정보에 모순이 발생할 수 있음.
  • 단위가 있는 숫자는 예외임. ex) temperatureC
 

item 31

  • strickNullChecks를 사용하자.
  • null 값이 암시적으로 관련되도록 설계하지 말자.
// 버그, 설계 결함이 있는 코드 const extent = (nums: number[]) => { let min; let max; for (const num of nums) { if (!min) { min = num; max = num; } else { min = Math.min(min, num); max = Math.max(max, num); // 이 부분에서 에러 } } return [min, max]; }; // min, max가 0인 경우 if문에서 falsy해짐. // nums 가 빈 배열이라면 [undefined, undefined]를 반환함. // 개선한 코드 function extent(nums: number[]) { let result: [number, number] | null = null; for (const num of nums) { if (!result) { result = [num, num]; } else { result = [Math.min(num, result[0]), Math.max(num, result[1])]; } } return result; }
  • api 작성 시 반환 타입을 큰 객체로 만들고 반환 타입 전체가 null이거나 null이 아니게 만들어야 함.
// 다음과 같이 두 번의 네트워크 요청이 로드되는 동안 user와 posts 속성은 null이 될 수 있음. async init(userId: string) { return Promise.all([ async () => this.user = await fetchUser(userId), async () => this.posts = await fetchPostsForUser(userId) ]); } // 개선한 코드 static async init(userId: string): Promise<UserPosts> { const [user, posts] = await Promise.all([ fetchUser(userId), fetchPostsForUser(userId) ]); return new UserPosts(user, posts); }
 

item 32

  • 타입끼리의 충돌이 일어날 경우 각각 타입의 계층을 분리된 인터페이스로 둬야 함.
// bad interface Layer { layout: FillLayout | LineLayout | PointLayout; paint: FillPaint | LinePaint | PointPaint; } // good.(각각 타입의 계층을 분리된 인터페이스로 두기) interface FillLayer { type: 'fill'; layout: FillLayout; paint: FillPaint; } interface LineLayer { type: 'line' layout: LineLayout; paint: LinePaint; } interface PointLayer { type: 'paint' layout: PointLayout; paint: PointPaint; } type Layer = FillLayer | LineLayer | PointLayer;
// bad interface Person { name: string; placeOfBirth?: string; dateOfBirth?: Date; } // good interface Person { name: string; birth?: { place: string; date: Date; } } // 타입 구조를 바꾸기 어렵다면 다음과 같이 써도 같은 효과를 볼 수 있음. interface Name { name: string; } interface PersonWithBirth extends Name { placeOfBirth: string; dateOfBirth: Date; } type Person = Name | PersonWithBirth;
 

item 33

  • 모든 문자열을 할당할 수 있는 string 타입보다는 더 구체적인 타입을 사용하자.
  • 객체의 속성 이름을 함수 매개변수로 받을 때 string 보다 keyof T를 사용하자.
// 이렇게 쓰기 보다 interface Album { artist: string; title: string; releaseDate: string; recordingType: string; } // 이렇게 쓰자 type RecordingType = 'live' | 'studio'; interface Album { artist: string; title: string; releaseDate: Date; recordingType: RecordingType; }
// 이렇게 작성하면 반환되는 타입이 (string | Date)[]가 되는데, const pluck = <T>(recors: T[], key: keyof T) => { return records.map(r => r[key]); } // 이렇게 작성하면 반환되는 타입이 Date[] 가 된다. // 또한 호출 부분에 있어서 매개변수 타입이 정밀해진 덕분에 자동완성 기능을 제공해준다. const pluck = <T, K extends keyof T>(records: T[], key: K) => { return records.map(r => r[key]); }
 

item 34

  • 부정확한 타입보다 미완성 타입 사용하기.
  • 정의가 어렵다면 any와 unknown 구별해서 타입을 지정하자.
 

item 35

  • 라이브러리 쓸 때 @types/~~ 를 통해 타입을 추가할 수 있음.
  • grpahQL을 쓴다면 자체적으로 타입을 생성할 수 있음. Apollo로 타입스크립트 타입으로 변환도 가능.
  • p.191쪽 하단 예시 잘못됐나? 파리미터 g가 쓰이지 않음.
 

item 36

  • 타입 변수명 잘 짓자.
 

item 37

  • _brand로 상표를 붙일 수 있음.
type AbsolutePath = string & {_brand: 'abs'}; function listAbsolutePath(path: AbsolutePath) { // ... } function isAbsolutePath(path: string): path is AbsolutePath { return path.startsWith('/'); }