똑같은 삽질은 2번 하지 말자

Effective Typescript vol.6 (타입 선언과 @types) 본문

카테고리 없음

Effective Typescript vol.6 (타입 선언과 @types)

곽빵 2022. 11. 20. 21:47

개요

이펙티브 타입스크립트라는 책을 읽고 장별(각각의 장안에 아이템별)로 학습한 내용 정리하기

 

이 책의 목표 

독자에게 타입스크립트나 자바스크립트를 가르치는 것이 아니라, 초급자나 중급자가 전문가로 발전할 수 있게 돕는 것

프로그래밍에서 어떤 방법을 가르치는게 아니라 어떤 방법을 사용할 때  그래야 하는지를 알려준다.

 

이 장을 통해 알수 있는 것

타입스크립트에서 의존성이 어떻게 동작하는지, 의존성에 대한 개념, 의존성을 관리하는 방법, 그리고 프로젝트를 공개하기 전에 타입 선언 파일을 작성하는 이유와 어떻게 작성해야 하는지에 대해 알아볼 수 있다.

 

Item45 devDependencies에 typescript와 @types 추가하기

  • typescript를 시스템 레벨(npm install -g typescript)로 설치하면 안 된다. typescript를 프로젝트의 devDep에 포함시키고 팀원 모두가 동일한 버전을 사용하도록 해야 한다.
  • @types 의존성은 dependencies가 아니라 devDependencies에 포함시켜야 한다. 타입스크립트는 런타임에 필요한 경우가 거의 없기때문에 만약 필요하다면 별도의 작업이 필요할 수 있다.

Item46 타입 선언과 관련된 세 가지 버전 이해하기

  • 타입 선언과 관련된 세가지 버전은 라이브러리 버전, @types 버전 typescript의 자체 버전등이 있고, 이 세가지의 버전에 따라 타입스크립트 생태계에선 더욱 의존성이 관리가 복잡하게 되었다.
  • 라이브러리를 업데이트하는 경우, 해당 @types 역시 업데이트를 하자.
  • 라이브러리를 만들때 타입을 DefinitelyTyped에 공개( =@types)해 라이브러리 본체와 따로 두는 방법과 타입 선언을 라이브러리에 포함시키는 것 두가지의 방법이 있는데, 라이브러리가 만약 타입스크립트로 작성된 라이브러리라면 타입 선언을 자체적으로 포함하는게 좋고 자바스크립트로 작성된 라이브러리라면 타입을 DefinitelyTyped에 공개해 따로 관리하는게 좋다.

타입스크립트로 작성된 라이브러리가 자체적으로 타입을 포함시키고 있는지를 보려면 package.json에 이하의 코드가 있을꺼다.

types의 .d.ts를 추가하고 번들링을 해서 타입 선언을 포함시키면 일심동체가 되서 문제가 없을꺼 같지만, 이것도 4가지의 문제점이 있는데 그것들에 대해서는 본 책을 읽어보자

{
  "name": "fast-glob",
  "version": "3.2.7",
  "description": "It's a very fast and efficient glob library for Node.js",
  "types": "out/index.d.ts",
  "main": "out/index.js",
}

 

Item47 공개 API에 등장하는 모든 타입을 익스포트 하기

  • 공개 메서드에 등장한 어떤 형태의 타입이든 익스포트 하자. 어차피 라이브러리 사용자가 추출할 수 있으므로 익스포트 하기 쉽게 만드는 것이 좋다. (숨기는게 거의 불가능하다. 라는 것)
interface SecretName {
  firstName: string;
  lastName: string;
}
interface SecretSanta {
  name: string;
  gift: string;
}

export const getGift = (name: SecretName, gift: string): SecretSanta => {
  return {
    name: name.firstName + name.lastName,
    gift,
  };
};

// 이런식으로 타입을 추출할 수 있다.
type MySanta = ReturnType<typeof getGift>;
type MyName = Parameters<typeof getGift>;

Item48 API 주석에 TSDoc 사용하기

  • TSDoc이란 우리가 쓰던 JSDoc을 말하는 것이다. 주석을 // 이게 아니고 /**/ 이친구를 이용하는 것 이걸 이용하는게 해당 함수를 사용하는 곳에서 주석을 툴팁으로 표시되게 할 수 있다.
  • 주석에 타입 정보를 포함하지는 말자. 타입 정보는 타입구문으로만 끝낼 수 있도록!

Item49 콜백에서 this에 대한 타입 제공하기

  • this 바인딩이 동작하는 원리를 이해해야 한다. 함수에서의 this는 실행 컨텍스트에 의해 window를 가리키게 되고 객체 안의 this를 그 객체 자신을 가리키고 화살표 함수에서는 상위의 this를 가리키게 된다.
  • 콜백 함수에서 this를 사용해야 한다면, 타입 정보를 명시해야 한다. this는 동적 스코프를 가지기 때문에 예상하기 어렵기 때문!

Item50 오버로딩 타입보다는 조건부 타입을 사용하기

  • 오버로딩 타입보다 조건부 타입을 사용하는 것이 좋다. 조건부 타입은 추가적인 오버로딩 없이 유니온 타입을 지원할 수 있기 때문이다.

구체적인 예를 살펴보자.

타입의 오버로딩을 이용해 복수의 타입이 올 수 있는 double함수를 만들었지만 number | string에 대한 유니온 타입은 밑과 같이 타입 에러가 발생하고 있다.

function double(x: number): number;
function double(x: string): string;

function double(x: any) {
  return x + x;
}

function f(x: number | string) {
  return double(x);
  // No overload matches this call.
  // Overload 1 of 2, '(x: number): number', gave the following error.
  //   Argument of type 'string | number' is not assignable to parameter of type 'number'.
  //     Type 'string' is not assignable to type 'number'.
  // Overload 2 of 2, '(x: string): string', gave the following error.
  //   Argument of type 'string | number' is not assignable to parameter of type 'string'.
  //     Type 'number' is not assignable to type 'string'.ts(2769)
}

이때 또 세번째 오버로딩(number | string)을 추가하는 것 보다는 조건부 타입을 사용해 보자.

function double<T extends number | string>(
  x: T
): T extends string ? string : number;

const num = double(12); // const num: number
const str = double("x"); // const str: string

// function f(x: number | string): string | number
function f(x: number | string) {
  return double(x);
}

이 하나의 조건부 타입으로 세개의 타입 오버로딩을 커버할 수 있다.

 

Item51 의존성 분리를 위해 미러 타입 사용하기

  • 필수가 아닌 의존성을 분리할 때는 구조적 타이핑을 사용하면 된다.

타입스크립트에서 구조적 타이핑이란 어떤 타입에 들어있는 모든 요소를 가지고 있기만 하면 그 어떤 타입으로써 말할 수 있다!

라는 의미인데 예를 들면 이하의 코드를 보면 바로 이해된다.

export default {};
interface Person {
  name: string;
  age: number;
}

const developer = { name: "heewon", age: 30, position: "front-end" };

function testFunction(param: Person) {
  console.log(param);
}

testFunction(developer); // developer를 position이라는 프로퍼티가 있음에도 Person로써 인정받음

이러한 구조적 타이핑의 특징을 이용해 라이브러리를 만들때 타입스크립트가 아닌 자바스크립트 사용자가 @types 의존성을 가지지 않게끔 @types를 devDependency로써 가지고 있는게 아니라 필요한 부분만 타입을 가져와 정의해두고 @types와의 호환성의 부분은 구조적 타이핑으로 해결하는 미러타입을 활용하는게 좋다. (@types는 또 타입스크립트 개발자에겐 필요하기 때문에)

 

Item52 테스팅 타입의 함정에 주의하기

  • 타입을 테스트할 때는 특히 함수 타입의 동일성과 할당 가능성의 차이점을 알고 있어야 한다. (?)
  • 콜백이 있는 함수를 테스트할 때 콜백 매개변수의 추론된 타입을 체크해야 한다. this가 API의 일부분이라면 역시 테스트해야 한다.
  • 타입 관련된 테스트에서 any를 주의하자. 더 엄격한 테스트를 위해 dtslint같은 도구를 활용하는 것도 좋다.
Comments