타입 좁히기

타입 넓히기의 반대는 타입 좁히기(Type narrowing)입니다. 타입 좁히기는 타입스크립트가 넓은 타입으로부터 좁은 타입으로 진행하는 과정을 말합니다.

null 체킹

가장 일반적인 예시는 null 체킹입니다. TS가 문맥 상 해당 변수가 null이 아님을 확신할 수 있을 경우, 타입에서 null이 제거됩니다.

const el = document.getElementById('foo'); // Type is HTMLElement | null
if (el) {
  el // Type is HTMLElement
  el.innerHTML = 'Party Time'.blink();
} else {
  el // Type is null
  alert('No element #foo');
}
const el = document.getElementById('foo'); // Type is HTMLElement | null
if (!el) throw new Error('Unable to find #foo');
el; // Now type is HTMLElement
el.innerHTML = 'Party Time'.blink();

instanceof

function contains(text: string, search: string|RegExp) {
  if (search instanceof RegExp) {
    search  // Type is RegExp
    return !!search.exec(text);
  }
  search  // Type is string
  return text.includes(search);
}

프로퍼티 체크

interface A { a: number }
interface B { b: number }
function pickAB(ab: A | B) {
  if ('a' in ab) {
    ab // Type is A
  } else {
    ab // Type is B
  }
  ab // Type is A | B
}

내장함수 사용

function contains(text: string, terms: string|string[]) {
  const termList = Array.isArray(terms) ? terms : [terms];
  termList // Type is string[]
  // ...
}

Tagged Union (Discriminated Union)

interface UploadEvent { type: 'upload'; filename: string; contents: string }
interface DownloadEvent { type: 'download'; filename: string; }
type AppEvent = UploadEvent | DownloadEvent;

function handleEvent(e: AppEvent) {
  switch (e.type) {
    case 'download':
      e  // Type is DownloadEvent
      break;
    case 'upload':
      e;  // Type is UploadEvent
      break;
  }
}

User-Defined Type Guards (사용자 정의 타입 가드)

TS가 타입을 적절히 식별하도록 하기 위해, 커스텀 함수를 직접 작성하여 타입 좁히기에 관여할 수 있습니다.

// 해당 함수가 true를 반환한다면 `el`은 HTMLInputElement 타입으로 좁혀집니다.
function isInputElement(el: HTMLElement): el is HTMLInputElement {
  return 'value' in el;
}

function getElementContent(el: HTMLElement) {
  if (isInputElement(el)) {
    el; // Type is HTMLInputElement
    return el.value;
  }
  el; // Type is HTMLElement
  return el.textContent;
}
const jackson5 = ['Jackie', 'Tito', 'Jermaine', 'Marlon', 'Michael'];

function isDefined<T>(x: T | undefined): x is T {
  return x !== undefined;
}

const members = ['Janet', 'Michael'].map(
  who => jackson5.find(n => n === who) // Type is (string | undefined)[]
).filter(isDefined);  // Type is string[]