Skip to content

Instantly share code, notes, and snippets.

@2color
Last active January 9, 2023 15:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 2color/2df8c35f5123871d8e67831777d419c3 to your computer and use it in GitHub Desktop.
Save 2color/2df8c35f5123871d8e67831777d419c3 to your computer and use it in GitHub Desktop.
Snippets and Learnings from Effective TypeScript

Learnings

Types as sets of values

keyof with union (|) and intersection (&)

There are no keys that TypeScript can guarantee belong to a value in the union type, so keyof for the union must be the empty set (never). Or, more formally:

keyof (A&B) = (keyof A) | (keyof B)
keyof (A|B) = (keyof A) & (keyof B)

If you can build an intuition for why these equations hold, you’ll have come a long way toward understanding TypeScript’s type system!

interface Point { 
  x: number;
  y: number;
}

type PointKeys = keyof Point; // Type is "x" | "y"

function sortBy<K extends keyof T, T>(vals: T[], key: K): T[] { 
  // ...
}

const pts: Point[] = [{x: 1, y: 1}, {x: 2, y: 0}];
sortBy(pts, 'x'); // OK, 'x' extends 'x'|'y' (aka keyof T)
sortBy(pts, 'y'); // OK, 'y' extends 'x'|'y'
sortBy(pts, Math.random() < 0.5 ? 'x' : 'y'); // OK, 'x'|'y' extends 'x'|'y' 
sortBy(pts, 'z');
             // ~~~ Type '"z"' is not assignable to parameter of type '"x" | "y"

Screen Shot 2021-02-26 at 11 21 50

Excess Property Checking

interface Room {
  numDoors: number;
  ceilingHeightFt: number;
}
const r: Room = {
  numDoors: 1,
  ceilingHeightFt: 10,
  elephant: 'present',
// ~~~~~~~~~~~~~~~~~~ Object literal may only specify known properties,
// and 'elephant' does not exist in type 'Room'
} 

const obj = {
  numDoors: 1,
  ceilingHeightFt: 10,
  elephant: 'present',
};
const r2: Room = obj; // OK
  • When you assign an object literal to a variable or pass it as an argument to a function, it undergoes excess property checking.
  • Excess property checking is an effective way to find errors, but it is distinct from the usual structural assignability checks done by the TypeScript type checker. Conflating these processes will make it harder for you to build a mental model of assignability.
  • Be aware of the limits of excess property checking: introducing an intermediate variable will remove these checks.

Item 14: Use Type Operations and Generics to Avoid Repeating Yourself

Ways to avoid duplication for the following two types:

interface State {
  userId: string;
  pageTitle: string;
  recentFiles: string[];
  pageContents: string;
}
interface TopNavState {
  userId: string;
  pageTitle: string;
  recentFiles: string[];
}

Using a mapped type

type TopNavState = {
  [k in 'userId' | 'pageTitle' | 'recentFiles']: State[k]
}

Using the Pick generic type

type TopNavState = Pick<State, 'userId' | 'pageTitle' | 'recentFiles'>;

Using generic types to extend a type

interface Name {
  first: string
  last: string
}
type DancingDuo<T extends Name> = [T, T]

const couple1: DancingDuo<Name> = [
  { first: 'Fred', last: 'Astaire' },
  { first: 'Ginger', last: 'Rogers' },
] // OK

Thinking of types as sets of values

Thinking of types as sets of values, it helps to read “extends” as “subset of” here.

type Pick<T, K extends keyof T> = { 
  [k in K]: T[k] 
}; // OK

This is also an example of index signatures. What should you use index signatures for? The canonical case is truly dynamic data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment