이펙티브 타입스크립트(3)

남자김용준·2021년 11월 5일
1

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

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

const r : Room = {
  numDoors: 1,
  ceilingHeightFt: 10,
  elephant: 'present',
  //~~~~~~~~~~~~~~~~~~~~ 개체 리터럴은 알려진 속성만 지정할 수 있으며
  //                     'Room' 형식에 'elephant'가 없습니다.
}

const obj = {
  numDoors: 1,
  ceilingHeightFt: 10,
  elephant: 'present'
};

const r : Room = obj; // 정상

객체 리터럴을 변수에 할당하거나 함수에 매개변수로 전달할 때 잉여 속성 체크가 수행된다.

잉여 속성 체크는 오류를 찾는 효과적인 방법이지만, 타입스크립트 타입 체커가 수행하는 일반적인 구조적 할당 가능성 체크와 역할이 다르다. 할당의 개념을 정확히 알아야 잉여 속성 체크와 일반적인 구조적 할당 가능성 체크를 구분할 수 있다.

잉여 속성 체크에는 한계가 있다. 임시 변수를 도입하면 잉여 속성 체크를 건너뛸 수 있다는 점을 기억해야 한다.

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

function rollDice1(sides: number): number {/*...*/}        // 문장
const rollDice2 = function(sides: number):number {/*...*/} // 표현식
const rollDice3 = (sides: number) : number => {/*...*/}    // 표현식
type DiceRollFn = (sides: number) => number;
const rollDice: DiceRollFn = sides => {/*...*/}

매개변수나 반환 값에 타입을 명시하기보다는 함수 표현식 전체에 타입 구문을 적용하는 것이 좋다.

만약 같은 타입 시그니처를 반복적으로 작성한 코드가 있다면 함수 타입을 분리해 내거나 이미 존재하는 타입을 찾아보도록 한다. 라이브러리를 직접 만든다면 공통 콜백에 타입을 제공해야 한다.

다른 함수의 시그니처를 참조하려면 typeof fn을 사용하면 된다.

const checkedFetch : typeof fetch = async (input,init) => {
  const response = await fetch(input,init);
  if(!response.ok){
  	throw new Error('Request failed: '+response.status);
  }
  return response;
}

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

타입스크립트에서 명명된 타입을 정의하는 방법은 두 가지가 있다.

type TState = {
  name : string;
  capital: string;
}

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

명명된 타이은 인터페이스로 정의하든 타입으로 정의하든 상태에는 차이가 없다.

const errorIState : IState = {
  name:'test',
  capital: 'capital',
  population: 500_000
  // ~~~~~~~~~~~~~~~~~~ 개체 리터럴은 알려진 속성만 지정할 수 있으며 IState 형식에 population 속성이 없습니다.
}
const errorTState : TState = {
  name:'test',
  capital: 'capital',
  population: 500_000
  // ~~~~~~~~~~~~~~~~~~ 개체 리터럴은 알려진 속성만 지정할 수 있으며 TState 형식에 population 속성이 없습니다.
}

인덱스 시그니처는 인터페이스와 타입에서 모두 사용할 수 있다.

type TDict = {[key:string]:string};
interface IDict {
  [key:string]:string;
}

함수 타입도 인터페이스나 타입으로 정의할 수 있다.

type TFn = (x:number) => string;
interface IFn {
  (x:number):string;
}
const toStrT:TFn = x => '' + x;
const toStrI:IFn = x => '' + x;

모두 제너릭이 가능하다.

type TPair<T> = {
  first : T;
  second: T;
}

interface IPair<T>{
  first: T;
  second: T;
}

유니온 타입은 있지만 유니온 인터페이스라는 개념은 없다.

type AorB = 'a' | 'b';

인터페이스는 타입과 달리 '보강'이 가능하다.

interface IState {
  name: string;
  capital: string;
}
interface IState {
  population: number;
}
const wyoming: IState = {
  name: 'Wyoming',
  capital: 'Cheyenne',
  population:500_000
}; //  정상

프로젝트에서 어떤 문법을 사용할지 결정할 때 한 가지 일관된 스타일을 확립하고, 보강 기법이 필요한지 고려해야한다.

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

DRY (Don't repeat yourself) 원칙을 타입에 최대한 적용해야한다.

타입에 이름을 붙여서 반복을 피해야 한다. extends를 사용해서 인터페이스 필드의 반복을 피해야 한다.

interface Person {
  firstName : string,
  lastName : string
}

interface PersonWithBirthDate extends Person {
  birth : Date;
}

type PersonWithBirthDate = Person & { birth:Date };

타입들 간의 매핑을 위해 keyof, typeof, 인덱싱 등의 도구들을 알아야 한다.

제너릭 타임은 타입을 위한 함수와 같다. 타입을 반복하는 대신 제너릭 타입을 사용하여 타입들 간에 매핑을 하는 게 좋다.

제너릭 타입을 제한하려면 extends를 사용하면 된다.

표준 라이브러리에 정의된 Pick, Partial, ReturnType 같은 제너릭 타입에 익숙해져야 한다.

type Pick<T,K> = { [k in K] : T[k] };

//example
type TopNavState = Pick<State, 'userId' | 'pageTitle' | 'recentFiles' >;

interface Options {
  width: number;
  height: number;
  color: string;
  label: string;
}
interface OptionsUpdate {
  width?: number;
  height?: number;
  color?: string;
  label?: string;
}

class UIWidget {
  constructor(init:Options){/*...*/}
  update(options:OptionsUpdate){/*...*/}
}

class UIWidget {
  constructor(init:Options){/*...*/}
  update(options:Partial<Options>){/*...*/}
}

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

type Rocket = {[property:string] : string};
cosnt rocket: Rocket = {
  name:'Falcon 9',
  variant: 'v1.0',
  thrust: '4,940 kN',
}

[property:string] :string 이 인덱스 시그니처이며 다음 세 가지 의미를 가지고 있다.
키의 이름 :키의 위치만 표시하는 용도이다. 타입 체커에서는 사용하지 않는다.
키의 타입 :string이나 number 또는 symbol의 조합이어야 하지만, 보통은 string을 사용한다.
값의 타입 :어떤 것이든 될 수 있다.

type Vec3D = Record<'x' | 'y' | 'z', number>;
//Type Vec3D = {
//  x : number;
//  y : number;
//  z : number;
//}

런타임 때까지 객체의 속성을 알 수 없을 경우에만 인덱스 시그니처를 사용하자. (csv파일에서 로드하는 경우와 같을 때)

안전한 접근을 위해 인덱스 시그니처의 값 타입에 undefined를 추가하는 것을 고려해야한다.

가능하면 인터페이스, Record, 매핑된 타입 같은 인덱스 시그니처보다 정확한 타입을 사용하는 게 좋다.

profile
frontend-react

0개의 댓글