Applied

TypeScript

Applied TypeScript

Course repository: https://github.com/MoonHighway/applied-typescript

 

Required: Node.js >= v18.0.0

Recommended: Visual Studio Code (with GitHub Copilot disabled)

 

Setup

If you only have a README in your cloned copy of the repository, run:

  git pull

Install dependencies by running:

  npm install

Agenda

  • Section 1: Composing Types: Part I
    • Generic types, Conditional types
  • Section 1: Composing Types: Part II
    • keyof type operator, Mapped types, Template literal types
  • Section 2: Improving Type Safety
    • Discriminated unions, Type predicates, Assertion signatures, Branded types, satisfies operator

Composing Types: Part I

Basic ways of composing types

  • Intersection types

  • Extending interfaces

  • Union types

  • Indexed access types

Basic ways of composing types

Intersection types

{ name: string; } & { age: number; }

Basic ways of composing types

Extending interfaces

interface User {
  name: string;
}

interface SuperAdmin extends User {
  enabled: boolean;
}

Basic ways of composing types

Union types

string | number | boolean
null | { value: number; }

Basic ways of composing types

Indexed access types

type User = {
  name: string;
  age: number;
}

type Age = User["age"];
// type Age = string;

Generic types

type Country<LanguagesType> = {
  name: string;
  languages: LanguagesType;
};

Type variable

Type parameter

Conditional types

type BoxIsShape = Box extends Shape ? boolean : unknown;

Conditional type constraint

True

branch

Box is assignable

to Shape

False

branch

Composing Types: Part II

keyof type operator

Produce a string or numeric literal union of the keys of an object type

interface User {
  name: string;
  age: number;
}

type UserKeys = keyof User;
// type UserKeys = "name" | "age";

Mapped types

Iterate through properties of an object type to create a new object type

type CountryData = {
  name: string;
  languages: string[];
  population: number;
};

type Descriptions<Type> = {
  [Key in keyof Type]: string;
};

Template literal types

Enforce specific string formats or expand string literal unions

type Greeting = `Welcome ${string}!`;

const greeting: Greeting = "Welcome home!";

Same syntax as template

literal strings in JavaScript

Improving Type Safety

Narrowing: Type guards

if (typeof population === "number") {
if (location instanceof City) {

Automatically recognised by TypeScript and used to narrow the type of a value

Narrowing: User-defined type guards

function valueIsString(value: any): value is string {

Type predicates - return true if value is of type

Assertion signatures - throw error if value is not of type

function assertIsString(
  value: unknown
): asserts value is string {

Narrowing: Discriminated unions

type Product = {
  name: string;
  price: number;
};

type Service = {
  name: string;
  price: number;
};

type CatalogItem = Product | Service;

const catalog: CatalogItem[] = [...];

Narrowing: Discriminated unions

discriminate /dĭ-skrĭm′ə-nāt″/
intransitive verb

 

1. To make a clear distinction; distinguish.

    "discriminate among the options available."

2. To perceive or notice the distinguishing features of; recognize as distinct.
    "unable to discriminate colors."

 

Source: The American Heritage® Dictionary of the English Language, 5th Edition

Narrowing: Discriminated unions

type Product = {
  type: "product";
  name: string;
  price: number;
};

type Service = {
  type: "service";
  name: string;
  price: number;
};

type CatalogItem = Product | Service;

const catalog: CatalogItem[] = [...];

Discriminant property

Discriminant property

Branded types

declare const __brand: unique symbol;

type Brand<BaseType, BrandedName extends string> = BaseType & {
  [__brand]: BrandedName;
};

// ----

type Cat = {
  name: string;
  color: string;
};

type BrandedCat = Brand<Cat, "Cat">;

Emulate nominal types by creating structurally unique types

A structurally unique type

TypeScript magic ✨

The satisfies operator

const configA = {
  logging: true,
  metricsKey: "app_metrics"
};

const configB: Record<string, boolean | string> = {
  logging: true,
  metricsKey: "app_metrics"
};

const configC = {
  logging: true,
  metricsKey: "app_metrics"
} satisfies Record<string, boolean | string>;

Ensure that an expression matches a type, but retain the specific inferred type