이펙티브 타입스크립트- 2장 타입 시스템

fullth·2022년 7월 20일
0

Effective TypeScript

목록 보기
2/6

effective-typescript

ITME13 타입과 인터페이스의 차이점 알기

type TState = {
  name: string;
  capital: string;
}
interface IState {
  name: string;
  capital: string;
}
  • 대부분의 경우는 타입을 사용해도 되고 인터페이스를 사용해도 무관하다.
    • 그러나 같은 상황에서는 동일한 방법으로 명명된 타입을 정의해 일관성을 유지해야 함.
    • 그러기 위해 하나의 타입에 대해 두 가지 방법을 모두 사용해 정의할 줄 알아야 함.
const wyoming: TState = {
  name: 'Wyoming',
  capital: 'Cheyenne',
  population: 500_000
// ~~~~~~~~~~~~~~~~~~ Type ... is not assignable to type 'TState'
//                    Object literal may only specify known properties, and
//                    'population' does not exist in type 'TState'
};
  • 둘 다 새로운 속성을 추가하면 오류 발생.
type TDict = { [key: string]: string };
interface IDict {
  [key: string]: string;
}
  • 둘 다 인덱스 시그니처 사용 가능.
  • 함수 타입도 가능.
  • 제너릭도 가능.
  • 인터페이스는 타입을 확장할 수 있고, 타입은 인터페이스를 확장할 수 있음.

\=> 프로젝트에서 어떤 문법을 사용할지 결정할 때 한 가지 일관된 스타일을 확립해야 함.

ITME14 타입 연산과 제너릭 사용으로 반복 줄이기

  • DRYYYYY!!!!!
function distance(a: {x: number, y: number}, b: {x: number, y: number}) {
  return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}

// 중복된 타입 개선
interface Point2D {
  x: number;
  y: number;
}
function distance(a: Point2D, b: Point2D) { /* ... */ }
  • 중복된 타입은 종종 문법에 의해 가려짐(문법에 의해 가려진다는게 무슨 말이지...)
    • 몇몇 함수가 같은 타입 시그니처를 공유할때 아래와 같이 해당 시그니처를 명명된 타입으로 분리가능
// HIDE
interface Options {}
// END
function get(url: string, opts: Options): Promise<Response> { /* COMPRESS */ return Promise.resolve(new Response()); /* END */ }
function post(url: string, opts: Options): Promise<Response> { /* COMPRESS */ return Promise.resolve(new Response()); /* END */ }

// 쩐당...
// HIDE
interface Options {}
// END
type HTTPFunction = (url: string, options: Options) => Promise<Response>;
const get: HTTPFunction = (url, options) => { /* COMPRESS */ return Promise.resolve(new Response()); /* END */ };
const post: HTTPFunction = (url, options) => { /* COMPRESS */ return Promise.resolve(new Response()); /* END */ };
  • 타입을 인덱싱해서 속성의 타입 중복을 제거할 수 있음.
interface State {
  userId: string;
  pageTitle: string;
  recentFiles: string[];
  pageContents: string;
}
type TopNavState = {
  userId: State['userId'];
  pageTitle: State['pageTitle'];
  recentFiles: State['recentFiles'];
};
  • 하지만 위 방식은 State가 변경되면 TopNavState에도 반영됨.
    • 매핑된 타입이란걸 이용해서 더 나은 코드로 개선할 수 있다함.
interface State {
  userId: string;
  pageTitle: string;
  recentFiles: string[];
  pageContents: string;
}
type TopNavState = {
  [k in 'userId' | 'pageTitle' | 'recentFiles']: State[k]
};

ITME15 동적 데이터에 인덱스 시그니처 사용하기

  • 타입스크립트에서는 인덱스 시그니처를 명시하여 유연하게 매핑을 표현할 수 있음.
type Rocket = {[property: string]: string};
const rocket: Rocket = {
  name: 'Falcon 9',
  variant: 'v1.0',
  thrust: '4,940 kN',
};  // OK
  • 자바스크립트는 문자열 키를 타입의 관계없이 매핑함.
  • Rocket 타입은 객체 타입. key는 string형, value도 string 형.
  • 이렇게 타입 체크가 수행되면 네 가지 단점이 드러남. 
    • 잘못된 키를 포함해 모든 키를 허용할 수 있음. ex.) name 이 아니라 Name도 ok.
    • 특정 키가 필요하지 않음. ex.) { }도 유효한 Rocket 타입.
    • 키마다 다른 타입을 가질 수 없음.
  • 인덱스 시그니처는 부정확함.
  • 다음과 같이 사용.
interface Rocket {
  name: string;
  variant: string;
  thrust_kN: number;
}
const falconHeavy: Rocket = {
  name: 'Falcon Heavy',
  variant: 'v1',
  thrust_kN: 15_200
};

=> 런타임까지 객체의 속성을 알 수 없을 경우에만 인덱스 시그니처를 사용 ex.) CSV파일에서 로드하는 경우

ITME16 number 인덱스 시그니처보다는 Array, 튜플, ArrayLike를 사용하기

  • 자바스크립트에서는 인덱스의 숫자키를 허용하지 않지만, 타입스크립트에서는 가상의 개념으로 숫자 키를 허용함.
  • 자바스크립트에서의 배열은 객체이므로 키는 숫자가 아니라 문자열임. 

ITME17 변경 관련된 오류 방지를 위해 readonly 사용하기

  • 타입 안전성을 높히기 위해서 매개변수를 수정하지 않는 함수에 readonly를 사용.
  • 이 방법은 인터페이스를 명확하게 하는 동시에 매개변수가 변경되는 것을 방지할 수 있음.
  • 변경되면 안되는데 변경이 발생하는 코드도 쉽게 찾을 수 있음.
  • readonly는 얕게 동작한다고 함. (여기서 얕다는 것은 객체의 얕은 복사, 깊은 복사의 개념에서와 동일한 의미를 지님.)
profile
Web Backend Developer

0개의 댓글