[독서] 이펙티브 타입스크립트 아이템 11 - 15

dante Yoon·2021년 9월 4일
0

독서

목록 보기
6/38
post-thumbnail

독서 후 개인적으로 정리한 내용을 기록한 글입니다.

아이템 11 잉여 속성 체크의 한계 인지하기

타입이 선언된 변수에 객체 리터럴을 할당할때 타입스크립트는 정의된 타입 이외의 잉여 속성이 함께 할당되었는지 체크한다.

interface Room {
  numDoors: number;
  ceilingHeightFt: number;
}

const r: Room = {
  numDoors: 1,
  ceilingHeightFt: 10,
  elephant: "present", // 타입 에러
}

구조적 타이핑 관점에서 생각하면, const r 에 할당한 객체 리터럴이 interface Room 의 모든 속성을 포함하기 때문에 에러가 발생하지 않아야 한다.
임시 변수를 도입해보면, 같은 객체를 할당했지만 타입 에러가 발생하지 않는다.

const obj = {
  numDoors: 1,
  ceilingHeightFt: 10,
  elephant: "present",
};
const r: Room = obj; // 정상

객체 리터럴을 할당한 첫 예제에서는 잉여 속성 체크라는 과정이 수행되었다.
이는 변수에 타입을 명시적으로 선언했을 때 우리가 기대하는 할당 가능 검사 와는 별도의 과정이라는 것을 알아야 한다.

Options 인터페이스 타입의 변수에는 documentHTMLAnchorElement 모두 할당이 가능한데, Options가 정말 넓은 타입이라는 것을 알 수 있다.

interface Options {
  title: string;
  darkMode?:boolean
}
const o1: Options = document;
const o2: Options = new HTMlAnchorElement;

잉여 속성 체크엄격한 객체 리터럴 체크라고도 하며 객체리터럴을 사용하지 않은 할당이나 타입 단언문을 사용할 때에도 적용되지 않는다.

const o = {darkmode: true, title: "free" } as Options  // 정상

잉여 속성 체크를 원치 않는다면 인덱스 시그치러를 사용할 수 있다.

interface Options {
  darkMode?: boolean;
  [other: string]: known;
}
const o:Options = {darkmode: true}; // 정상

아이템 12 함수 표현식에 타입 적용하기

자바스크립트에서는 함수를 문정과 표현식으로 나타낼 수 있다.

function rollDice(side:number): number {} // 문장
const rollDices = function(side: number): number {}; // 표현식

함수 매개변수부터 반환값까지 전체를 함수 타입으로 선언하여 다른 함수 표현식에서 재사용할 수 있기 때문에, 문장 보다는 표현식으로 사용하는 것이 낫다.

type DiceFn = (sides: number) => number;
const rollDice: DiceFn = sides => {};

만약 같은 타입 시그니처를 반복적으로 작성한 코드가 있다면, 라이브러리에서 불러오는 함수일 경우 이미 존재하는 함수 타입이 있는지 찾아보거나, 함수타입을 따로 분리해내는 것이 좋다.

아이템 13 타입과 인터페이스의 차이점 알기

면접에서 단골 질문으로 등장할 것 같은 주제이다.
type, interface는 명명된 타입(named type)이라고 부르며 두 가지중 어느 것을 사용하더라도 타입을 나타내는데 문제가 없다.

cf) 본 책에서는 interface의 접두어로 대문자 I, type의 접두어로 대문자 T를 사용하는 것을 추천하지 않는데, 이런 코드 스타일은 C#에서 비롯된 관례라고 한다. 현재는 지양해야할 스타일이며 표준 라이브러리에서도 일관성 있게 도입되지 않았으므로 사용하지 말자.

인터페이스는 타입을 확장할 수 있으며, 타입은 인터페이스를 확장할 수 있다.

inteface IStateWithPop extends TState{
  population number;
}
type TStateWithPop IState & {population: number};

인터페이스는 타입을 확장할 수는 있지만 유니온은 할 수 없다.

type AorB = "a" | "b";
type NamedVariable = (Input | Output) & {name: string};	

인터페이스는 NamedVariable 과 같은 타입을 표현할 수 없다.

튜플 타입은 인터페이스보다 타입으로 표현하는 것이 용이하다.

type Pair = [number, number];
type SomeNums = [string, ...number[]];

인터페이스로도 튜플과 비슷하게 구현할 수 있으나 prototype.concat과 같은 메서드를 사용할 수 없다.

interface Tuple {
  0: number;
  1: number;
  length: 2;
}
const tuple: Tuple =[1, 2];

인터페이스는 타입과 다르게 보강(augment)가 가능하다. 이렇게 속성을 확장하는 것을 선언 병합(declaration merging)이라고 한다.

interface IState {
  name: string;
  capital: string;
}
interface IState {
  population: number;
}

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

같은 코드를 반복하지 말라는 DRY(don't repeat yourself)원칙이 있다.
타입 중복 또한 코드 중복만큼 피해야 한다.
타입이 에서 공유된 패턴을 제거하는 일은 자바스크립트에서 중복된 코드를 제거하는 것보다 생소하게 느껴질 수 있기 때문에 타입 간에 매핑하는 방법을 익힌다면 타입스크립트에서도 DRY 원칙을 지킬 수 있다.

전체 애플리케이션의 상태를 표현하는 State와 부분만 나타내는 PositionState 있을 때 속성 타입을 다시 작성하거나 State를 상속하지 않고 State의 부분집합으로 PositionState를 표현할 수 있다.

interface State {
  x: string;
  y: string;
  height: number;
  width: number;
}
type PositionState = {
  x: State["x"];
  y: State["y"];
}

매핑된 타입을 사용해보자. 매핑된 타입은 Pick 유틸리티 타입으로 대체할 수도 있다.

type PositionState = {
  [k in "x" |"y"]: State[k]
}

type PositionState = Pick<State, "x" | "y">

타입에 이름을 붙여서 반복을 피하고 extends를 사용해서 인터페이스 필드의 반복을 피하는 것이 좋다.
제너릭 타입은 타입을 위한 함수와 같다. 타입을 반복하는 대신 제너릭 타입을 사용하여 매핑해보자.
표준 라이브러리에 정의된 Pick, Partial, ReturnType과 같은 제너릭 타입에 익숙해져야 한다.

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

인덱스 시그니처는 객체의 키, 밸류를 다음과 같이 표현한 것이다.

type Something = {[property: string]: string};
1. 이때 key는 string, number, symbol 중 하나가 되어야 한다. 
2 인덱스 시그니처는 잘못된 키를 포함해 모든 키를 허용한다. 
3.name 대신 Name 과 같이 키를 표현해도 유효한 타입으로 인정된다.
4.키마다 다른 타입을 가질 수 없다. name이 string이면서 index라는 키가 있다고 할 때 number타입으로 지정할 수 없다. 
5.자동완성 기능이 동작하지 않는다. 

csv 파일과 같은 형식을 인풋으로 받아 parsing 하는 함수가 있다고 할 때, 행과 열에 어떤 이름을 가진 데이터가 있는지 모르므로 이렇게 동적인 데이터가 들어올 때는 인덱스 시그니처를 사용한다.

매핑된 타입을 사용하며 특정 키에 대해 다른 타입을 사용하기 원한다면 다음과 같이 할 수 있다.

type ABC = {[k in "a" | "b" | "c"]: k extends "b" ? string : number};
// Type ABC = {
//  a: number;
//  b: string;
//  c: number;
// }

런타임 때까지의 객체 속성을 알 수 없을 경우에는 인덱스 시그니처를 사용한다.

profile
성장을 향한 작은 몸부림의 흔적들

0개의 댓글