Union types

A union type is formed from two or more types. A value of that type may be any one of the members.

function printId(id: number | string) {
  console.log("Your ID is: " + id);
}

printId(101); // OK
printId("202"); // OK
printId({ myID: 22342 });
// Error: Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.

You can also write leading | for readability:

function printTextOrNumberOrBool(
  textOrNumberOrBool: string | number | boolean,
) {
  console.log(textOrNumberOrBool);
}

Working with unions (narrowing)

TypeScript only allows operations that are valid for every member of the union. So this fails:

function printId(id: number | string) {
  console.log(id.toUpperCase());
  // Error: Property 'toUpperCase' does not exist on type 'string | number'.
}

Narrow the type with the same checks you would write in JavaScript:

function printId(id: number | string) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id);
  }
}

Array.isArray works the same way:

function welcomePeople(x: string[] | string) {
  if (Array.isArray(x)) {
    console.log("Hello, " + x.join(" and "));
  } else {
    console.log("Welcome lone traveler " + x);
  }
}

If every member shares a method, you can call it without narrowing:

function getFirstThree(x: number[] | string) {
  return x.slice(0, 3);
}

Both arrays and strings have slice.

Type aliases

A type alias names any type so you can reuse it:

type Point = {
  x: number;
  y: number;
};

function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}

printCoord({ x: 100, y: 100 });

Aliases work for unions and primitives too:

type ID = number | string;

Aliases are only names : they do not create a distinct runtime type:

type UserInputSanitizedString = string;

function sanitizeInput(str: string): UserInputSanitizedString {
  return str.trim();
}

let userInput = sanitizeInput("  hello  ");
userInput = "new input"; // still allowed : both are just string

Practice

type Status = "idle" | "loading" | "error";

function handle(id: ID, status: Status) {
  // narrow id, then log status
}
  1. Define ID as number | string.
  2. Implement handle so string ids are uppercased and number ids are printed as-is.
  3. Pass an invalid status like "done" and fix the call site.