-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
아이템 13. 타입과 인터페이스의 차이점 알기
- 대부분의 경우 타입과 인터페이스 모두 사용해도 되지만 차이를 아고, 일관성을 유지해야 한다.
IT접두사를 붙이는 네이밍 방식은 지양해야 한다. (C#에서 유래)
인터페이스 선언과 타입 선언의 비슷한 점
- 인덱스 시그니처는 인터페이스와 타입에서 모두 사용할 수 있다.
type TDict = {[key: string]: string};
interface IDict = {
[key: string]: string;
}- 함수 타입도 인터페이스나 타입으로 정의할 수 있다.
type TFn = (x: number) => string;
interface IFn {
(x: number): string;
}- 타입 별칭과 인터페이스는 모두 제너릭이 가능하다.
type TPair<T> = {
first: T;
second: T;
};
interface IPair<T> {
first: T;
second: T;
}- 인터페이스는 타입을 확장할 수 있으며, 타입은 인터페이스를 확장할 수 있다.
인터페이스는 유니온 타입 같은 복잡한 타입을 확장하지 못하므로 이런 경우에는 타입과 &를 사용해야 한다.
interface IStateWithPop extends TState {
population: number;
}
type TStateWithPop = IState & { population: number };- 클래스를 구현할 때는 타입과 인터페이스 둘 다 사용할 수 있다.
class StateT implements TState {
name: string = "";
capital: string = "";
}
class StateI implements IState {
name: string = "";
capital: string = "";
}인터페이스 선언과 타입 선언의 다른 점
- 유니온 타입은 존재하지만 유니온 인터페이스는 존재하지 않는다.
- 일반적으로
type키워드는 쓰임새가 더 많다.
type NamedVariable = (Input | Output) & { name: string };
type Pair = [number, number];
type StringList = string[];
type NamedNums = [string, ...number[]];
// 인터페이스로 튜플 구현
interface Tuple {
0: number;
1: number;
length: 2;
}
const t: Tuple = [10, 20]; // 정상
// 🥲 concat과 같은 메서드는 사용할 수 없음- 인터페이스에는 타입에 없는 선언 병합(declaration merging) 기능을 사용할 수 있다.
interface IState {
name: string;
capital: string;
}
interface IState {
population: number;
}
const wyoming: IState = {
name: "Wyoming",
capital: "Cheyenne",
population: 500000,
};- 선언 병합은 주로 타입 선언 파일에서 사용된다.
- 타입은 기존 타입에 추가적인 보강이 없는 경우에만 사용해야 한다.
- 프로젝트 내부적으로 사용되는 타입에 선언 병합이 발생하는 것은 잘못된 설계이다.
아이템 14. 타입 연산과 제너릭 사용으로 반복 줄이기
Don't repeat yourself : 같은 코드를 반복하지 말라.
- 확장을 사용하여 반복을 제거할 수 있다.
- 이미 존재하는 타입을 확장하는 경우에, 일반적이지 않지만 인터색션 연산자를 사용할 수도 있다.
- 매핑된 타입은 배열의 필드를 루프 도는 것과 같은 방식이다. 이 패턴은 표준 라이브러리에서도 일반적으로 찾을 수 있으며,
Pick이라고도 한다.
type Pick<T, K> = { [k in K]: T[k] };
type TopNavState = Pick<State, "userId" | "pageTitle" | "recentFiles">;type OptionsUpdate = { [k in keyof Options]?: Options[k] };
// 흔한 패턴이므로 이미 표준 라이브러리에 Partial로 존재- 제너릭 타입은 타입을 위한 함수와 같다. 타입을 반복하는 대신 제너릭 타입을 사용하여 타입들 간에 매핑을 하는 것이 좋다.
- 제너릭 타입에서 매개변수를 제한할 수 있는 방법은
extends를 사용하는 것이다. - 표준 라이브러리에 정의된 Pick, Partial, ReturnType 같은 제너릭 타입에 익숙해져야 한다.
interface Name {
first: string;
last: string;
}
type DancingDuo<T extends Name> = [T, T];
const couple1: DancingDuo<Name> = [
{ first: "Fred", last: "Astaire" },
{ first: "Ginger", last: "Rogers" },
]; // OK
const couple2: DancingDuo<{ first: string }> = [
{ first: "Sonny" },
{ first: "Cher" },
]; // ERROR : { first: string; } 타입은 Name을 확장하지 않음 ❌
type FirstLast = Pick<Name, "first" | "last">; // 정상
type FirstMiddle = Pick<Name, "first" | "middle">; // ERROR : 'middle'을 first' | 'last'에 할당할 수 없음아이템 15. 동적 데이터에 인덱스 시그니처 사용하기
- 인덱스 시그니처는 동적 데이터를 표현할 때 사용한다.
- 런타임 때까지 객체의 속성을 알 수 없을 경우에만 인덱스 시그니처를 사용하자
// EX : CSV 파일처럼 헤더 행에 열 이름이 있고, 데이터 행을 열 이름과 값으로 매핑하는 객체로 나타내고 싶은 경우
function parseCSV(input: string): { [columnName: string]: string }[] {
const lines = input.split("\n");
const [header, ...rows] = lines;
const headerColumns = header.split(",");
return rows.map((rowStr) => {
const row: { [columnName: string]: string } = {};
rowStr.split(",").forEach((cell, i) => {
row[headerColumns[i]] = cell;
});
return row;
});
}
// 열 이름이 무엇인지 미리 알 방법이 없으므로 인덱스 시그니처 사용interface Row1 {
[column: stirng]: number;
} // 너무 광범위
interface Row1 {
a: number;
b?: number;
c?: number;
d?: number;
} // 최선
type Row3 =
| { a: number }
| { a: number; b: number }
| { a: number; b: number; c: number }
| { a: number; b: number; c: number; d: number }; // 번거로움- 인덱스 시그니처를 사용하기에는 string 범위가 광범위 하다면 두 가지 대안이 있다.
// 1. Record 사용
type Vec3D = Record<"x" | "y" | "z", number>;
// 2. 매핑된 타입 사용
type Vec3D = { [k in "x" | "y" | "z"]: number };
type Vec3D = { [k in "x" | "y" | "z"]: k extends "b" ? string : number }; // a: number; b: string; c:number;아이템 16. number 인덱스 시그니처보다는 Array, 튜플, ArrayLike를 사용하기
- 타입스크립트는 자바스크립트의 배열 키가 문자열로 처리되는 혼란을 바로잡기 위해 숫자 키를 허용하고, 문자열 키와 다른 것으로 인식한다. 이는 실제로는 동작하지 않고 런타임에 타입 정보가 제거된다.
// Array에 대한 타입 선언
interface Array<T> {
// ...
[n: number]: T;
}- 어떤 길이를 가지는 배열과 비슷한 형태의 튜플을 사용하고 싶다면 타입스크립트에 있는
ArrayLike타입을 사용한다.
아이템 17. 변경 관련된 오류 방지를 위해 readonly 사용하기
readonly number[]는 타입이다. 배열의 요소를 읽을 수 있지만, 쓸 수는 없으며 length를 변경할 수 없다. 또한 pop을 비롯한 배열을 변경하는 다른 메서드를 호출할 수 없다.
매개변수를 readonly로 설정하면?
- 타입스크립트는 매개변수가 함수 내에서 변경이 일어나는지 체크한다.
- 호출하는 쪽에서는 함수가 매개변수를 변경하지 않는다는 보장을 받는다.
- 호출하는 쪽에서 함수에 readonly 배열을 매개변수로 넣을 수도 있다.
👀 readonly는 얕게 동작한다! 깊게 동작하려면 ts-essentials의 DeepReadonly 제너릭을 사용해야 한다.
아이템 18. 매핑된 타입을 사용하여 값을 동기화하기
- 인터페이스 새로운 속성을 추가할 때, 선택을 강제하도록 매핑된 타입을 고려해야 한다.