[이펙티브 타입스크립트] - 아이템46 ~ 아이템54

Lee Jeong Min·2022년 4월 7일
1

TypeScript

목록 보기
17/18
post-thumbnail

[아이템46] 타입 선언과 관련된 세 가지 버전 이해하기

타입스크립트를 사용하는 경우 의존성 관리 측면에서 다음 세 가지의 사항을 추가로 고려해야한다.

  • 라이브러리 버전
  • 타입 선언(@types)의 버전
  • 타입스크립트의 버전

세가지 버전 중 하나라도 맞지 않으면 오류가 발생할 수 있다.

라이브러리와 타입 정보의 버전이 별도로 관리되는 방식의 문제점

  1. 라이브러리를 업데이트했지만 실수로 타입 선언은 업데이트 하지 않은 경우 → 타입오류 발생하여 다음과 같은 기법 사용
    1. 보강 기법을 통한 타입 정보를 프로젝트 자체에 추가
    2. 타입 선언의 업데이트를 직접 작성하고 공개하여 커뮤니티에 기여
  2. 라이브러리보다 타입 선언의 버전이 최신인 경우
    1. 타입 선언의 버전이 맞도록 라이브러리 버전을 올리거나 타입 선언의 버전을 내리는 방법으로 해결
  3. 프로젝트에서 사용하는 타입스크립트 버전보다 라이브러리에서 필요로 하는 타입스크립트 버전이 최신인 경우
    1. 프로젝트의 버전을 높이거나 라이브러리 타입 선언의 버전을 원래대로 내림
  4. @types 의존성이 중복될 수 있다.

일반적으로 ts라이브러리들은 자체적으로 타입 선언을 포함(번들링)하게 된다. → index.d.ts 파일을 가리키게함 그러나 이러한 번들링 방식은 부수적인 4가지 문제점을 가짐

  1. 번들된 타입 선언에 보강 기법으로 해결할 수 없는 오류가 있는 경우, 또는 공개 시점에는 잘 동작했지만 타입스크립트 버전이 올라가면서 오류가 발생하는 경우 문제
  2. 프로젝트 내의 타입 선언이 다른 라이브러리의 타입선언에 의존하면 문제 발생
  3. 프로젝트의 과거 버전에 있는 타입 선언에 문제가 있는 경우, 과거 버전으로 돌아가서 패치 업데이트를 해야함
  4. 타입 선언의 패치 업데이트를 자주 하기는 어려운문제

공식적인 권장사항은 라이브러리가 TS로 작성된 경우만 타입선언을 라이브러리에 포함시키는것!

JS로 작성된 라이브러리는 타입 선언을 DefinitelyTyped에 공개하여 커뮤니티에서 관리하고 유지보수하도록 맡기는 것이 좋다!

[아이템47] 공개 API에 등장하는 모든 타입을 익스포트하기

interface SecretName {
  first: string;
  last: string;
}

interface SecretSanta {
  name: SecretName;
  gift: string;
}

export function getGift(name: SecretName, gift: string): SecretSanta {
  // ...
}

이렇게 특정 타입을 익스포트하지 않고 숨겨도 아래와 같이 타입은 노출된다.

type MySanta = ReturnType<typeof getGift>; // SecretSanta
type MyName = Parameters<typeof getGift>[0]; // SecretName

→ 따라서 라이브러리 사용자를 위해 굳이 타입을 숨기려하지말고 명시적으로 익스포트 하자!

[아이템48] API 주석에 TSDoc 사용하기

인라인 스타일의 주석보다 JSDoc을 사용하면 툴팁으로 아래와 같이 표시해준다.

→ 따라서 인라인 스타일 대신 JSDoc 스타일의 주석을 달자

타입스크립트 관점에서는 TSDoc이라고 하는데, JSDoc과 달리 @params, @returns와 같은 타입 정보를 명시하는 규칙대신 타입 정보가 코드에 있기 때문에 TSDoc에서는 타입 정보를 명시하면 안된다. 또한 TSDoc과 JSDoc은 마크다운 형식으로 문서를 서식할 수 있다.(강조, 기울임 등등..)

[아이템49] 콜백에서 this에 대한 타입 제공하기

이 장은 주로 this에 관해 다루는데 JS의 this 동작 방식을 알고 있다면 쉽게 이해할 수 있다.

이 장의 대부분의 내용은 this의 동작방식에 대해 설명하고 있어서 TypeScript와 관련된 부분은 아래와 같다.

function addKeyListener (
  el: HTMLElement,
  fn: (this: HTMLElement, e:KeyboardEvent) => void
) {
  el.addEventListener('keydown', e => {
    fn(e); // this 바인딩 체크
  })
}

타입스크립트에서 콜백 함수의 첫 번째 매개변수에 있는 this는 특별하게 처리된다. callthis 바인딩 없이 매개변수를 2개 넘기면 1개의 인수만을 원한다고 하며, this 바인딩을 체크해주기 때문에 실수를 방지해준다.

→ 콜백 함수에서 this를 사용해야 한다면, 타입 정보를 명시하자!

[아이템50] 오버로딩 타입보다는 조건부 타입을 사용하기

오버로딩으로 타입에 따른 함수들을 다 정의하기보다, 아래와 같이 조건부 타입을 이용하면 유니온 타입 또한 조건부 타입의 유니온으로 분리되어 잘 작동한다.

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

오버로딩 타입보다 조건부 타입을 사용하여 간단한게 작성하자.

[아이템51] 의존성 분리를 위해 미러 타입 사용하기

미러타입: 라이브러리에서 필요한 선언부만 추출하여 작성중인 라이브러리에 넣는 것

function parseCSV(contents: string | Buffer): { [column: string]: string }[] {
  if (typeof contents === 'object') {
    // 버퍼인경우
    return parseCSV(contents.toString('utf-8'));
  }
}

위와 같은 코드가 있을 때, Nodejs를 사용자를 위한 Buffer 타입이 존재하지만 이는 타입과 무관한 자바스크립트 개발자나 Nodejs와 무관한 타입스크립트 웹 개발자들에게는 의미가 없다.

→ 따라서 이러한 경우 타입을 devDependencies로 추가하기보다, 각자가 필요한 모듈만 사용할 수 있도록 구조적 타이핑을 적용하면 좋다. 그러나 타입의 대부분을 추출해야하는 경우는 명시적으로 @types 의존성을 추가하는게 낫다.

[아이템52] 테스팅 타입의 함정에 주의하기

이 아이템에선 테스트코드의 타입을 체크하기 위해 반환 타입을 체크하는 과정에서 발생하는 여러 문제들을 다루고 있다.

최종적으로 이 장에서 말하고자 하는 바는 DefinitelyTyped의 타입 선언을 위한 도구로 dtslint를 소개하며 이를 사용하는 것을 추천한다.

const beatles = ['john', 'paul', 'george', 'ringo'];
map(beatles, function(
  name, // $ExpectType string
  i, // $ExpectType number
  array // $ExpectType string[]
) {
  this // $ExpectType string[]
  return name.length;
}) // $ExpectType number[]

dtslint를 사용한 테스트코드

dtslint는 할당 가능성을 체크하는 대신 각 심벌의 타입을 추출하여 글자 자체가 같은지 비교한다. 그러나 이러한 방식에도 단점이 있는데, number|stringstring|number는 같은 타입이지만 글자 자체로 보면 다르기 때문에 다른 타입으로 인식된다.

7장

[아이템53] 타입스크립트 기능보다는 ECMAScript 기능을 사용하기

자바슼릡트가 초기에는 결함이 많고 개선해야하는 부분이 많은 언어여서 타입스크립트에 다음과 같은 신규기능들이 있었다.

  • 열거형(enum)
  • 매개변수 속성
  • 네임스페이스와 트리플 슬래시 임포트
  • 데코레이터

그러나 각각 자바스크립트와 잘 호환되지 않고, 타입스크립트의 역할을 명확하게하는 측면에서 이것들을 사용하지 않는 것이 좋다!

[아이템54] 객체를 순회하는 노하우

const obj = {
  one: 'uno',
  two: 'dos',
  three: 'tres',
}

for (const k in obj) {
  const v = obj[k] // 에러발생
}

// 다음과 같이 사용
let k: keyof typeof obj;
for (k in obj) {
  const v = obj[k]
}

k와 obj 객체의 키 타입이 서로 다르게 추론되어 오류가 발생한다. 이 경우 keyof을 사용하여 해결한다.

interface ABC {
  a: string;
  b: string;
  c: number;
}

function foo(abc: ABC) {
  for (const k in abc) {
    const v = abc[k]; // 에러 인덱스 시그니처가 없고 암시적 'any'가 되므로
  }
}

const x = { a: 'a', b: 'b', c: 2, d: new Date() };
foo(x); // 정상

위 경우 인터페이스 ABC에는 구조적타이핑에 따라 a, b, c외에 다른 속성이 존재할 수 있는 타입이 foo함수 내부로 들어올 수 있기 때문에 타입스크립트는 ABC 타입의 키를 string 타입으로 선택하게 된다.

여기서 맨 위에서 해결한 keyof을 사용하개 되면 인터페이스의 타입이 한정되고, 새로운 타입이 들어왔을 때 문제가 발생한다. → 이 경우 Object.entries를 사용하면 좋다.

또한 프로토타입의 오염을 신경쓰면서 속성을 추가하고, for-in 문은 프로토타입 체인상에 존재하는 프로퍼티들을 보여주기 때문에 이 점을 유의하자!

요약

  • 객체 순회시, 키가 어떤 타입인지 정확하게 파악하고 있다면 keyof Tfor-in 루프를 사용하자. 또한 함수의 매개변수로 쓰이는 객체에는 추가적인 키가 존재할 수 있다는 점을 명시!
  • 객체를 순회하며 키와 값을 얻는 가장 일반적인 방법은 Object.entries를 사용하는 것이다.
profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글