Dev/etc.

이펙티브 타입스크립트 아이템 41 ~ 50

takeU 2023. 6. 28. 10:08
반응형

아이템 41 - any의 진화를 이해하기

  • 일반적인 타입들은 정제되기만 하는 반면, 암시적 anyany[]타입은 진화할 수 있음
function range(start: number, limit: number) {
  const out = [];  // Type is any[]
  for (let i = start; i < limit; i++) {
    out.push(i);  // Type of out is any[]
  }
  return out;  // Type is number[]
}
  • any를 진화시키는 방식보다 명시적 타입 구문을 사용하는 것이 안전한 타입을 유지하는 방법

아이템 42 - 모르는 타입의 값에는 any 대신 unknown을 사용하기

  • 함수의 반환값에 타입 선언을 강제할 수 없기 때문에, 호출한 곳에서 타입 선언을 생략하게 되면 사용되는 곳마다 타입 오류가 발생
  • 차라리 unknown을 반환하게 만드는 것이 더 안전

any가 강력하면서 위험한 이유

  • 어떠한 타입이든 any타입에 할당 가능
  • any타입은 어떤 타입으로도 할당 가능

집합의 관점에서 any는 타입 시스템과 상충되는 면을 가지고 있음
따라서 타입 체커가 무용지물이 됨을 주의해야 함

unknown

  • any 대신에 사용할 수 있는 타입 시스템에 부합하는 타입
  • 위에 any의 설명에서 첫 번째 문장에만 부합
    • unknownunknownany에만 할당 가능
    • never 타입은 두 번째 문장만 만족
  • unknown 타입을 그대로 사용하는 것이 아닌, 적절한 타입으로 변환 후 사용
  • 즉, 어떠한 값이 있지만 타입을 알지 못할 때 사용

아이템 43 - 몽키 패치보다는 안전한 타입을 사용하기

  • 타입 체커는 임의로 추가한 속성에 대해 알지 못함
  • any 단언문 (a as any) 사용해 해결하면 간단하지만, 타입 안정성을 상실
  • 따라서 데이터를 documentDOM으로부터 분리
  1. interface의 보강 기법 사용
interface Document {
  monkey: string;
}

document.monkey = 'Tamarin' // 정상
  • any를 사용할 때보다 나은 점
    • 타입이 더 안전함
    • 속성에 주석을 붙일 수 있음
    • 속성에 자동완성 사용 가능
    • 몽키 패치가 어떤 부분에 적용되었는지 기록이 남음
// 모듈 관점에서 제대로 동작하게 하기 위해서는 global 선언 추가
declare global {
  interface Document { ~~ }
}

모듈 영역 (scope)에 주의해 사용해야 함

  1. 더 구체적인 타입 단언문 사용
interface MonkeyDocument extends Document {
  monkey: string;
}
(document as MonkeyDocument).monkey = 'Macaque'
  • 몽키패치는 남용하면 안 되며 궁극적으로 더 잘 설계된 구조로 리팩토링하는것이 좋음

아이템 44 - 타입 커버리지를 추적하여 타입 안정성 유지하기

noImplicitAny를 설정하고 모든 암시적 any 대신 명시적 타입 구문을 추가해도 any 타입과 관련된 문제들로부터 안전하다 할 수 없음
any 타입이 여전히 존재할 수 있는 두 가지 경우

  1. 명시적 any 타입
  • any 타입의 범위를 좁히고 구체적으로 만들어도 여전히 any 타입인 경우
  • any[]{[key: string]: any}
  1. 서드파티 타입 선언
  • @types 선언 파일로부터 any 타입 전파

type-cover-age 패키지로 any 추적

npx type-coverage로 추적

아이템 45 - devDependencies에 typescript와 @types 추가하기

주요 의존성

  • dependencies
    • 필수적인 라이브러리들이 포함
  • devDependencies
    • 프로젝트를 개발하고 테스트하는데 사용되지만, 런타임에는 필요없는 라이브러리들
  • peerDependencies
    • 런타임에는 필요하지만, 의존성을 직접 관리하지 않는 라이브러리들

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

타입스크립트를 사용할 때 고려해야 할 세 가지

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

세가지 버전 중 하나라도 맞지 않으면 오류가 발생

라이브러리 버전이 타입 선언버전보다 최신인 경우

  • 라이브러리 업데이트와 관련된 새로운 기능을 사용할 때마다 타입 오류 발생

  • 하위 호환성이 깨지는 변경이 있었다면, 타입 체커를 통과해도 런타임에서 오류가 발생할 수 있음

  • 해결책

    • 타입 선언도 버전을 업데이트 해 버전을 맞춤
    • 타입 선언의 버전이 준비되지 않은 경우
      • 보강(augmentation) 기법 활용 / 새 함수나 메소드의 타입 정보를 프로젝트 자체에 추가
      • 타입 선언의 업데이트를 직접 작성해 커뮤니티에 기여

라이브러리보다 타입 선언의 버전이 최신인 경우

  • 라이브러리 버전을 올리거나 타입 선언의 버전을 내림

프로젝트에서 사용하는 타입스크립트 버전보다 라이브러리에서 필요로 하는 타입스크립트 버전이 최신인 경우

  • 프로젝트의 타입스크립트 버전을 올림
  • 라이브러리 타입 선언의 버전을 내림
  • declare module 선언으로 라이브러리 타입 정보를 없앰

@types 의존성이 중복될 경우

  • 중복되는 타입을 업데이트해 버전이 호환되도록 함
    • 해당 방식은 의존성을 만들어 문제를 일으킬 수 있음

타입 번들링 방식의 네 가지 문제점

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

의존성을 잘 관리한다면?

  • 잘 작성된 타입 선언은 라이브러리를 올바르게 사용하는 방법을 배우는 데 도움이 되며 생산성을 크게 향상시킬 수 있음

라이브러리를 만들 때 권장사항

  • 타입 선언을 자체적으로 포함하는 것과, 타입 정보만 분리하여 DefinitelyTyped에 공개하는 것의 장단점을 비교해보아야 함
  • 라이브러리가 타입스크립트로 작성된 경우만 타입 선언을 라이브러리에 포함

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

  • 라이브러리 사용자를 위해 명시적으로 모든 타입을 익스포트하는것이 좋음

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

  • JSDoc 형태(/** 주석 */)로 작성해야, 편집기에서 주석을 툴팁으로 표시해 줌
  • @param, @returns 같은 일반적 규칙을 사용할 수 있음
  • 마크다운 사용 가능

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

  • this는 다이나믹 스코프 - 정의된 방식이 아니라 호출된 방식에 따라 달라짐
  • this 바인딩이 동작해야 하는 원리를 이해하고 사용해야 함
  • 콜백 함수에서 this를 사용해야 한다면, 타입 정보를 명시해야 함

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

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

// 1
function double(x: number|string): number|string;
function double(x: any) { return x + x }
double(12) // number 반환
double('x') // string 반환
// 하지만 선언문엔 string > number 도 포함

// 2. 제네릭 사용
function double<T extends number|string>(x: T): T;
// 타입이 과하게 구체적임

// 3. 여러 가지 타입 선언으로 분리
function double(x: number): number;
function double(x: string): string;
// 유니온 타입 관련해서 문제 발생
// string|number 타입은 string에 할당할 수 없음 
  • 오버로딩 타입보다 조건부 타입을 사용하는 것이 좋음
  • 조건부 타입은 추가적인 오버로딩 없이 유니온 타입을 지원할 수 있음

출처: 이펙티브 타입스크립트