Book Review/Effective Typescript

day3_Effective TypeScript 스터디

예 강 2023. 6. 16. 09:06
2022년 11월 29일 글입니다.

Item11. 잉여 속성 체크의 한계 인지하기

잉여속성체크 : 변수에 객체 리터럴을 할당할때, 해당 타입의 속성이 있는지, 있어선 안되는 ' 그외의 속성이 없는지' 체크하는 행위

  • 구조적 타입 시스템에서 발생할 수 있는 중요한 종류의 오류를 잡아줌


그러나 잉여 속성 체크는 조건에 따라 동작하지 않음. 오로지 객체 리터럴을 할당할 때 만 발동 * 잉여 속성 체크와 할당 가능 검사(구조적 타이핑)은 별도의 과정임

구조적 타입체커로만 사용했을 땐, 구조적 타이핑에 부합한다는 이유로 이상한 변수를 할당할 수도 있다. 그런 걸 막아주는게 잉여속성 체크, 타입에 정확하게 들어맞는지 검사해준다.

잉여 속성 체크의 한계

  1. 객체 리터럴이 아닌 변수 할당 같은 경우엔 적용 안됨
  2. as T 같은 타입 단언문을 사용할 때에도 적용 안됨
  • 잉여 속성 체크가 싫다면 [otherOptions : string]:unknown; 과 같은 인덱스 시그니쳐를 사용하면 된다.

Item12. 함수 표현식에 타입 적용하기

요약 1. 구조가 비슷한 함수가 있을때 매개변수나 반환 값에 타입을 명시하기보다는 함수 표현식 전체에 타입 구문을 적용하는 것이 좋다.
요약 2. 다른 함수의 시그니처를 참조하려면 typeof fn 을 사용하자.

이번 item에서는 함수표현식을 줄이는 방법을 알아보자

type BinaryFn = (a: numbert, b:number) => number;
const add : BinaryFn = (a, b) = a+b
const sub : BinaryFn = (a, b) = a-b
const mul : BinaryFn = (a, b) = a*b
const div : BinaryFn = (a, b) = a/b
declare function fetch(
	input: RequestInfo, init?:RequestrInit
): Promise<Response>;

async function checkedFetch(input: RequestInfo, init?:RequestInit){
const response = await fetch(input, init);
if(!response.ok){
	throw new Error(response.status);
}
return response;

더 간결하게 작성하기

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

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

요약 1. 타입과 인터페이스의 차이점을 분명히 알고 적절할 때 사용할 줄 알아야 한다.
요약2. 일관성과 보강의 관점에서 타입을 쓸지 인터페이스를 쓸지 고민하자.

  • 타입은 인터페이스와 타입 키워드를 이용해 생성할 수 있다.

차이점

  • 인터페이스는 타입을 확장 할 수 있다.
  • 타입은 인터페이스를 확잘할 수 있다.
  • type AorB = 'a' | 'b' 같은 유니온타입은 인터페이스로 확장하지 못하니 타입을 사용해야 한다.

  • 인터페이스는 선언 병합(declatation merging)을 사용할 수 있다.

간단하게 요약하자면 타입스크립트가 선언된 것들을 병합해서 전체 요소를 가지는 하나의 인터페이스를 돌려준다.

타입을 쓸까 인터페이스를 쓸까?

기본적으론 프로젝트의 일관성을 따라갈 것을 권장하며,

  • API의 타입 선언을 작성해야 할땐 API가 변경될 때를 대비하여 선언병합을 할 수 있는 인터페이스가 유리하고,
  • 프로젝트 내부적으로 사용되는 타입에 대해선 선언병합을 할 수 없는 타입을 사용해야 한다.

Item14. 타입연산과 제네릭 사용으로 반복 줄이기

요약 1. DRY(don't repeat yourself) 원칙을 타입에도 최대한 적용햐자
요약 2. 타입들 간의 매핑을 위한 도구들을 공부하자. keyof, typeof, 매핑된 타입들
요약 3. 제네릭 타입을 잘 활용해서 DRY원칙을 지키자
요약 4. Pic, Partial, ReturnType 같은 제네릭 타입에 익숙해지자.

  • 코드의 반복을 최대한 줄이자!
  1. 타입에 이름을 붙인다.

  1. 같은 시그니처를 사용하는 함수를 줄여보자

3.인터페이스 확장을 이용한 제거

  1. 한 타입이 어떤 타입의 부분집할일때, 인덱싱을 통해 중복 제거

이런식으로 중복되어있는 타입인데, 부분집합이라 확장하는 개념을 사용하기 어렵다.

  1. 여기서 끝나지 않았다. 매핑된 타입을 이용해 극한으로 줄여보자

자주 사용하는 패턴이라 표준 라이브러리에도 정의되어 있다. 익숙해지자.

  1. 타입을 선택적 필드로 매핑하기 Partial

자주 사용되는 패턴이라 표준 라이브러리에 포함되어있다.

  1. 값의 형태에 해당하는 타입을 정의하기 typeof

값으로 부터 타입을 만들어 낼 때는 선언의 순서에 주의해야 한다. 타입정의를 먼저하고 값을 선언해야 타입이 더 명확해진다고 한다.

  1. 반환된 값으로 타입을 만들고 싶을 때 ReturnType
  type UserInfo = ReturnType<typeof getUserInfo>
  1. 제네릭 타입에서 매개변수를 제한하는 방법 extends


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

요약 1. 런타임 때까지 객체의 속 성을 알 수 없을 경우에만(ex, csv파일에서 받아올 때) 인덱스 시그니처를 사용하자.
요약 2. 안전한 접근을 위해 인덱스 시그니처의 값타입에 undefined를 추가하는것을 고려하자(빠지는 값이 있을수도 있으니까)
요약 3. 가능하다면 인터페이스 Record, 매핑된 타입같은 인덱스 시그니처보단 정확한 타입을 쓰는게 좋다!

  • csv같은 파일을 읽어올 때, 일반적인 상황에선 열이름이 무엇인지 알 방법이 없다. 이럴 때 인덱스 시그니처를 사용하여 타입을 적용할 때 인덱스 시그니처를 사용하는게 좋다.
  • 보통 인덱스 시그니처는 오타같은 실수가 발생해도 에러를 못잡으니까 사용하지 않는게 좋다.
  • 런타임 상황에서 csv데이터에 값들이 실제로 존재하지 않을 수도 있으니 undefined를 추가해주는게 좋다.
function parseCSV(input: string): { [columnName: string]: string }[] {
  const lines = input.split("\n");
  const [header, ...rows] = lines;
  return rows.map(rowStr => {
    const row: { [columnName: string]: string } = {};
    rowStr.split(",").forEach((cell, i) => {
      row[header[i]] = cell;
    });
    return row;
  });
}

interface ProductRow {
  productId: string;
  name: string;
  price: string;
}

declare let csvData: string;
const products = parseCSV(csvData) as unknown as ProductRow[];
function safeParseCSV(input: string): { [columnName: string]: string | undefined }[] {
  return parseCSV(input);
}

  • 레코드를 사용해 키타입의 범위를 적당히 지정해 주는것도 좋다.

  • 매핑된 타입으로 제한할 수도 있다.