Effective typescript

Dan Vanderkam


Chapter 1

TypeScript is a superset of JavaScript. TypeScript adds a type system that models JavaScript’s runtime behavior and tries to spot code which will throw exceptions at runtime.

2 main options: noImplicitAny and strictNullChecks. Enable them in new projects and migrate to them in old.

Typescript has structural typing: “If it walks like a duck and talks like a duck…” It has advantages and downsides. “Your types are open”, so it’s possible to pass Vector3d into method for Vector2d. Iterating over objects can be tricky to type correctly because of open types.

The any type effectively silences the type checker and TypeScript language services. It can mask real problems, harm developer experience, and undermine confidence in the type system. Avoid using it when you can!

Chapter 2

  • Take advantage of the TypeScript language services by using an editor that can use them.
  • Use your editor to build an intuition for how the type system works and how TypeScript infers types.
  • Know how to jump into type declaration files to see how they model behavior.

The general rule is that the values in an intersection type contain the union of properties in each of its constituents.

// Union types. First or second
type Padding =  string | number
// Union types with common fields
interface Bird {
  fly(): void;
  layEggs(): void;
}
interface Fish {
  swim(): void;
  layEggs(): void;
}
type Pet = Bird | Fish // have only layEggs in common.
// Discriminating Unions. Use literal types to narrow down the possible current type.
type NetworkState =
  | NetworkLoadingState
  | NetworkFailedState
  | NetworkSuccessState;

function logger(state: NetworkState): string {
  switch (state.state) {
    case "loading":
      return "Downloading...";
    case "failed":
      // The type must be NetworkFailedState here,
      // so accessing the `code` field is safe
      return `Error ${state.code} downloading`;
    case "success":
      return `Downloaded ${state.response.title} - ${state.response.summary}`;
  }
}



// Intersection types. Props from both
interface ErrorHandling {
  success: boolean;
  error?: { message: string };
}
interface ArtworksData {
  artworks: { title: string }[];
}
type ArtworksResponse = ArtworksData & ErrorHandling;

Type operations apply to the sets of values (the domain of the type), not to the properties in the interface. And remember that values with additional properties still belong to a type.

interface PersonSpan extends Person { “Assignable to”, “subset of”, “subtype” - all about PersonSpan.

Tuple is a subset of a number.

A symbol in TypeScript exists in one of two spaces:

  • Type space
  • Value space instanceof - value space, tests to see if the prototype property of a constructor appears anywhere in the prototype chain of an object. typeof - mean different things in a type or value context. In a type context, typeof takes a value and returns its TypeScript type. In a value context, typeof is JavaScript’s runtime typeof operator. typeof always operates on values. You can’t apply it to types.
type T1 = typeof p; // Type is Person type T2 = typeof email;
// Type is (p: Person, subject: string, body: string) => Response const v1 = typeof p; // Value is "object"
const v2 = typeof email; // Value is "function"
const v1 = typeof p; // Value is "object"
const v2 = typeof email; // Value is "function"

Prefer Type Declaration to Type Assertions

const alice: Person = { name: 'Alice' }; // Type declaration
const bob = { name: 'Bob' } as Person; // Type assertion

As a suffix, ! is interpreted as an assertion that the value is non-null.

Type assertions have their limits: they don’t let you convert between arbitrary types. The general idea is that you can use a type assertion to convert between A and B if either is a subset of the other. Use type assertions and non-null assertions when you know something about types that TypeScript does not.

  • Avoid object wrapper types(Number,String, etc.) When you access a method like charAt on a string primitive, JavaScript wraps it in a String object, calls the method, and then throws the object away.

  • “excess property checking”, or “strict object literal checking” - disallowing unknown properties specifically on object literals. Excess property checking is an effective way of catching typos and other mistakes in property names that would otherwise be allowed by the structural typing system. Be aware of the limits of excess property checking: introducing an intermediate variable will remove these checks.

Item 14

Mapped type

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

// keyof - takes a type and gives you a union of the types of its keys:
/**
 * Make partial
 */
type Par = {[k in keyof F]?: F[k]}

Use extends to contstrain the parameters in a generic type.

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

// constraining K
// read "extends" as "subset of"
type Pick<T, K extends keyof T> = { [k in K]: T[k]
}; // OK

Item 15

Index signature. Prefer it only for dynamic data.

type S = {[k: string]: string}

There are almost always better alternative to index signature.

Item 16 Prefer Arrays, Tuples, and ArrayLike to number Index Signatures

Understand that arrays are objects, so their keys are strings, not numbers. number as an index signature is a purely TypeScript construct which is designed to help catch bugs. Prefer Array, tuple, or ArrayLike types to using number in an index signature yourself.

Item 17: Use readonly to Avoid Errors Associated with Mutation

An important caveat to readonly is that it is shallow. Use readonly to prevent errors with mutation and to find the places in your code where mutations occur

Item 18: Use Mapped Types to Keep Values in Sync

“Fail closed” and “Fail open” approaches. const REQUIRES_UPDATE: {[k in keyof ScatterProps]: boolean} = { - all keys from ScatterProps should be present. Useful when adding property to one object reflect on the logic of another.

Chapter 3 Type Inference

There is a difference between “statically typed” and “explicitly typed” because there is a type inference.

Item 19: Avoid Cluttering Your Code with Inferable Types

Consider using explicit annotations for object literals and function return types even when they can be inferred. This will help prevent implementation errors from surfacing in user code.

Item 20: Use Different Variables for Different Types

To avoid confusion, both for human readers and for the type checker, avoid reusing variables for differently typed values.

Item 21: Understand Type Widening

const mixed = ['x', 1]; Control widening using const. When you write as const after a value, TypeScript will infer the narrowest possible type for it.

Item 22: Understand Type Narrowing

Using if/else, instanceof, property check ("a" in ab), Array.isArray(). Another common way to help the type checker narrow your types is by putting an explicit “tag” on them:

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

This pattern is known as tagged union or discriminated union. User defined type guard: function isInputElement(el: HTMLElement): el is HTMLInputElement - help narrowing type.

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

Item 23: Create Objects All at Once

Spread operator is good at type recognition.

Chapter 4

Item 28: Prefer Types That Always Represent Valid States

Item 32: Prefer Unions of Interfaces to Interfaces of Unions

Item 33: Prefer More Precise Alternatives to String Types

Item 36

Reuse names from the domain of your problem where possible to increase the readability and level of abstraction of your code.

Chapter 5

Chapter 6

Item 47: Export All Types That Appear in Public APIs

Item 48: Use TSDoc for API Comments

Item 50: Prefer Conditional Types to Overloaded Declarations

Chapter 7

Item 53: Prefer ECMAScript Features to TypeScript Features

Chapter 8

Item 58: Write Modern JavaScript

Item 61: Convert Module by Module Up Your Dependency Graph

Item 62: Don’t Consider Migration Complete Until You Enable noImplicitAny