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);
}
// 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('/');
}