Dev/etc.

이펙티브 타입스크립트 아이템 21 ~ 30

takeU 2023. 6. 15. 15:32
반응형

아이템 21 - 타입 넓히기

넓히기 (widening)

  • 런타임에 모든 변수는 유일한 값을 가진다
  • 타입스크립트가 작성된 코드를 체크하는 정적 분석 시점에, 변수는 '가능한' 값들의 집합인 타입을 가진다
  • 상수를 사용해 변수를 초기화할 때 타입을 명시하지 않으면 타입 체커는 타입을 결정해야 한다
  • 즉, 단일 값을 가지고 할당 가능한 값들의 집합을 유추해야 한다
interface Vector3 { x: number; y: number; z: number; }
function getComponent(vector: Vector3, axis: 'x' | 'y' | 'z') {
  return vector[axis];
}

let x = 'x'
let vec = {x: 10, y: 20, z: 30}
getComponent(vec, x);
// 'string' 형식의 인수는 '"x" | "y" | "z"' 형식의 매개변수에 할당할 수 없습니다.

변수 x는 넓히기에 의해 string으로 추론되어 할당이 불가능해짐

const mixed = ['x', 1] // (string|number)[]

넓히기 과정을 제어하는 방법

  • const로 선언하기
  • 객체는 선언시 모양을 기준으로 추론되기 때문에, 한번에 만들어야 함
  1. 명시적 타입 구문 제공
const v: { x: 1|3|5 } = {
  x: 1,
} // 타입이 { x: 1|3|5 }
  1. 타입 체커에 추가적인 문맥 제공
    • 매개변수에 추가적인 문맥 전달
  2. const 단언문 사용
const v1 = {
  x: 1,
  y: 2
} // { x: number; y: number; }

const v2 = {
  x: 1 as const,
  y: 2
} // { x: 1; y: number; }

const v3 = {
  x: 1,
  y: 2
} as const // { readonly x: 1; redaonly y: 2; }

아이템 22 - 타입 좁히기

타입스크립트는 일반적으로 조건문에서 타입을 좁히는 데 매우 능숙합니다

  • null 체크
  • 분기문에서 예외를 던지거나 반환
  • instanceof
  • 속성 체크 ('a' in ab)
  • Array.isArray 같은 일부 내장 함수로도 가능
function contains(text: string, terms: string | string[]) {
  const termList = Array.isArray(terms) ? terms : [terms];
  termList; // 타입이 string[]
}

태그된/구별된 유니온

  • 명시적 '태그'를 붙여서 타입 좁히기
interface UploadEvent { type: 'upload' }
interface DownloadEvent { type: 'download' }
type AppEvent = UploadEvent | DownloadEvent;
function handleEvent(e: AppEvent) {
  switch(e.type) {
    case 'download':
      e; // 타입이 DownloadEvent
      break;
    case 'upload'
      e; // 타입이 UploadEvent
      break;
  }
}

사용자 정의 타입 가드

  • 타입 식별을 돕기 위한 커스텀 함수
function isInputElement(el: HTMLElement): el is HTMLInputElement {
  return "value" in el;
}

function getElementContent(el: HTMLElement) {
  if (isInputElement(el)) {
    el; // 타입이 HTMLInputElement
    return el.value;
  }
  el; // 타입이 HTMLElement
  return el.textContent;
}
  • 반환타입이 true일 경우 타입 체커에게 매개변수의 타입을 좁힐 수 있다고 전달
  • undefined가 올 가능성이 있을 때, 타입 가드를 사용하면 타입 좁히기 가능.

아이템 23 - 한꺼번에 객체 생성하기

// 객체는 한번에 생성하는 것이 좋음
const pt: Point = {
  x: 3,
  y: 4,
}

// 확장시 스프레드 연산자 활용
const namedPoint = { ...pt, ...id }

// 조건부 속성 확장 시

아이템 24 - 일관성 있는 별칭 사용하기

  • 별칭을 사용해서 원래의 속성값을 변경가능
    • 별칭을 남발해서 사용하면 제어 흐름을 분석하기 어려움
  • 객체 비구조화를 통해 일관된 이름을 사용하는 것이 좋음
    • 주의점
      1. 타입 경계에 null값을 추가하는 것이 좋음
      2. 없는 속성값은 나타내기
  • 타입스크립트는 함수가 타입 정제를 무효화하지 않는다고 가정하지만 실제로는 무효화될 가능성이 있음
  • 속성보다는 지역 변수를 사용하는 것이 타입 정제를 더욱 믿을 수 있음

아이템 25 - 비동기 코드에는 콜백 대신 async 함수 사용하기

Promiseasync/await을 콜백 대신 사용하는 이유

  1. 코드를 작성하기 쉬움
  2. 타입을 추론하기 쉬움

Promise 대신 async/await을 사용해야 하는 이유

  1. 일반적으로 더 간결하고 직관적
  2. async 함수는 항상 프로미스를 반환하도록 강제
    • 값을 통일하는데 도움이 됨
    • 또 다른 프로미스로 래핑되지 않기 때문에 타입 정보가 명확함

아이템 26 - 타입 추론에 문맥이 어떻게 사용되는지 이해하기

문맥을 고려한 타입 추론에는 가끔 이상한 결과나 나올 수 있음

type Language = 'JavaScript' | 'TypeScript' | 'Python';
function setLanguage(language: Language) {}

setLanguage('JavaScript') // 정상

let language = 'JavaScript';
setLanguage(language); // 'string' 형식의 인수는 'Language' 형식의 매개변수에 할당될 수 없습니다.

해결 방법

  1. 타입 선언에서 language의 가능한 값을 제한
  • 오타 오류를 표시해주는 장점이 있음
let language: Language = 'JavaScript';
setLanguage(language) // 정상
  1. language를 상수로 만듦
const language = 'JavaScript';

튜플 사용 시 주의점

function panTo(where: [number, number])

panTo([10, 20]) // 정상

const loc = [10, 20];
panTo(loc); // 'number[]' 형식의 인수는 '[number, number]' 형식의 매개변수에 할당될 수 없습니다.

해결 방법

  1. 타입 선언 제공
const loc: [number, number] = [10, 20]
  1. 상수 문맥 제공
const loc = [10, 20] as const
  • 타입 정의에 실수가 있다면 오류는 호출부에서 발생하므로 주의해야 함
  1. readonly 구문 추가
function panTo(where: readonly [number, number]) {}
const loc = [10, 20] as const;
panTo(loc);

객체 사용 시 주의점

튜플과 유사

콜백 사용 시 주의점

  • 타입스크립트는 콜백의 매개변수 타입을 추론하기 위해 문맥을 사용
  • 가능한 전체 함수 표현식에 타입 선언을 적용하는 것이 좋음

아이템 27 - 함수형 기법과 라이브러리로 타입 흐름 유지하기

내장 함수와 유틸리티 라이브러리를 사용해야 하는 이유

  1. 타입 흐름 개선
  2. 가독성 증가
  3. 명시적 타입 구문의 필요성을 줄임

아이템 28 - 유효한 상태만 표현하는 타입을 지향하기

  • 타입을 설계할 떄, 어떤 값을 포함하고 어떤 값을 제외할지는 신중하게 생각해야 함
  • 유효한 상태에서만 표현하는 타입을 지향해야 함
    • 무효한 상태 예시 - isLoadingerror 가 동시에 설정되는 상태

아이템 29 - 사용할 때는 너그럽게, 생성할 때는 엄격하게

  • 함수 시그니처에서 매개변수는 타입의 범위가 넓어도 되지만, 결과를 반환할 때는 타입의 범위가 더 구체적이어야 함
  • 매개변수와 변환 타입의 재사용을 위해서 기본 형태와 느슨한 형태를 도입하는 것이 좋음

아이템 30 - 문서에 타입 정보를 쓰지 않기

  • 주석에 타입 정보 적지 않기 / 모순 발생
  • 타입이 명확하지 않은 경우, 변수명에 단위 정보를 포함하는 것을 고려해야 함

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