DEV Community

Cover image for TypeScript types you can see running
JSLab
JSLab

Posted on

TypeScript types you can see running

TypeScript checks your code, then the types disappear. What runs is plain JavaScript. Most of the time that's invisible. The bugs live where it isn't.

The fastest way to build intuition is to run typed code and look at the values, not the types.

as is a promise, not a check

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

const raw: unknown = JSON.parse('{"name":"Ada","age":"36"}');
const user = raw as User;

user.age + 1;
Enter fullscreen mode Exit fullscreen mode

The editor is happy: user.age is a number. The value is "361". The JSON had "36" as a string, as User didn't check anything, and + concatenated.

as tells the compiler "trust me". Nothing at runtime holds you to it.

Type guards: a check the compiler believes

A type guard is a function that checks at runtime and tells the compiler what it proved:

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    typeof (value as User).name === "string" &&
    typeof (value as User).age === "number"
  );
}

isUser(raw);                                   // false
isUser({ name: "Ada", age: 36 });              // true
Enter fullscreen mode Exit fullscreen mode

Inside if (isUser(raw)) { … }, raw is a User, and this time it's true.

Assertion functions: fail loudly

When bad data should stop the program, an assertion function narrows the type for everything after it:

function assertUser(value: unknown): asserts value is User {
  if (!isUser(value)) {
    throw new TypeError(`Not a User: ${JSON.stringify(value)}`);
  }
}

assertUser(raw); // TypeError: Not a User: {"name":"Ada","age":"36"}
Enter fullscreen mode Exit fullscreen mode

One gotcha: assertion functions need an explicit type at the call site, so declare them with function (or annotate the const), or TypeScript refuses to narrow.

satisfies: check without widening

satisfies checks a value against a type but keeps the precise type you wrote:

const themes = {
  graphite: { dark: true },
  "graphite-light": { dark: false },
} satisfies Record<string, { dark: boolean }>;

themes.graphite.dark;   // fine
// themes.nord          // error: no such key
Enter fullscreen mode Exit fullscreen mode

Annotate it as Record<string, …> instead and themes.nord compiles, then is undefined at runtime.

Watching it happen

I write these in JSLab, a JavaScript & TypeScript scratchpad for macOS. Two things make it good for this:

  • Types are checked in the editor and stripped when the code runs, so a type error is an underline, never a failed run. You can write const n: number = "5";, see the underline and see that the value is "5".
  • Auto Log shows the value of each top-level expression beside its line. Every user.age + 1; above prints its real answer without a console.log.

Here's Auto Log in a real JSLab TypeScript tab: every expression on its own line shows its value beside it, xs.sort() on line 3, xs.length on line 4, through xs.at(-1) on line 11, with no console.log anywhere.

A JSLab tab called Auto Log, set to TypeScript. Each expression on its own line shows its value in the output: Array(3) tagged :3, 3 tagged :4, 3 tagged :9, Array(3) tagged :10 and 3 tagged :11.

Thrown errors land on their line too, so assertUser(raw) shows up as an error row tagged with the line that called it.

The short version

  • as and ! are claims. Use them when you know, not when you hope.
  • At the edges (JSON, fetch, localStorage), check with a guard or an assertion.
  • Prefer satisfies over an annotation when you want both the check and the precise type.

JSLab launches Nov 15. TypeScript in the editor: https://js-lab.app/docs/editor

Top comments (0)