GGistDev

Narrowing in TypeScript

Narrowing refines a union to a specific constituent via checks that the compiler understands.

Equality and truthiness

function f(x: number | null | undefined) {
  if (x == null) return 0;   // null or undefined
  return x + 1;              // number
}

function g(s: string | "") {
  if (!s) return 0;          // falsy narrows to ""
  return s.length;           // string
}

Prefer ?? for defaulting: x ?? 0.

typeof / instanceof / in

function id(x: string | number) {
  return typeof x === "number" ? x : Number(x);
}
class A {}
function h(v: A | Date) {
  if (v instanceof Date) return v.getTime();
  return v; // A
}
function hasName(x: unknown) {
  if (typeof x === "object" && x && "name" in x) {
    // x has name
  }
}

Discriminated unions

type Msg = { type: "text"; text: string } | { type: "img"; url: string };
function render(m: Msg) {
  if (m.type === "text") return m.text;  // Msg -> text
  return `<img src="${m.url}">`;         // Msg -> img
}

User-defined guards and asserts

function isStr(x: unknown): x is string { return typeof x === "string" }
function assertStr(x: unknown): asserts x is string { if (!isStr(x)) throw new Error("!string") }

Exhaustive checks

function exhaustive(x: never): never { throw new Error(String(x)) }

Use in switch default to ensure all cases are handled.

Summary

  • Combine checks and control flow; the compiler follows
  • Prefer discriminants and user-defined guards for clarity
  • Enforce exhaustiveness with never