Skip to content

Instantly share code, notes, and snippets.

@simonrelet
Created January 21, 2022 13:53
Show Gist options
  • Save simonrelet/2b5f1c9348850f8f782b9132fbcdd160 to your computer and use it in GitHub Desktop.
Save simonrelet/2b5f1c9348850f8f782b9132fbcdd160 to your computer and use it in GitHub Desktop.
Types and Values

Extracted from https://stackoverflow.com/a/50396312

TypeScript: A world of Types and Values

As you are likely aware, TypeScript adds a static type system to JavaScript, and that type system gets erased when the code is transpiled. The syntax of TypeScript is such that some expressions and statements refer to values that exist at runtime, while other expressions and statements refer to types that exist only at design/compile time. Values have types, but they are not types themselves. Importantly, there are some places in the code where the compiler will expect a value and interpret the expression it finds as a value if possible, and other places where the compiler will expect a type and interpret the expression it finds as a type if possible.

The compiler does not care or get confused if it is possible for an expression to be interpreted as both a value and a type. It is perfectly happy, for instance, with the two flavors of null in the following code:

let maybeString: string | null = null;

The first instance of null is a type and the second is a value. It also has no problem with

let Foo = { a: 0 };
type Foo = { b: string };

where the first Foo is a named value and the second Foo is a named type. Note that the type of the value Foo is { a: number }, while the type Foo is { b: string }. They are not the same.

Even the typeof operator leads a double life. The expression typeof x always expects x to be a value, but typeof x itself could be a value or type depending on the context:

let bar = { a: 0 };
let TypeofBar = typeof bar; // the value "object"
type TypeofBar = typeof bar; // the type { a: number }

The line let TypeofBar = typeof bar; will make it through to the JavaScript, and it will use the JavaScript typeof operator at runtime and produce a string. But type TypeofBar = typeof bar; is erased, and it is using the TypeScript type query operator to examine the static type that TypeScript has assigned to the value named bar.


Now, most language constructs in TypeScript that introduce names create either a named value or a named type. Here are some introductions of named values:

const value1 = 1;
let value2 = 2;
var value3 = 3;
function value4() {}

And here are some introductions of named types:

interface Type1 {}
type Type2 = string;

But there are a few declarations which create both a named value and a named type, and, like Foo above, the type of the named value is not the named type. The big ones are class and enum:

class Class {
  public prop = 0;
}

enum Enum {
  A,
  B,
}

Here, the type Class is the type of an instance of Class, while the value Class is the constructor object. And typeof Class is not Class:

const instance = new Class(); // value instance has type (Class)
// type (Class) is essentially the same as { prop: number };

const ctor = Class; // value ctor has type (typeof Class)
// type (typeof Class) is essentially the same as new() => Class;

And, the type Enum is the type of an element of the enumeration; a union of the types of each element. While the value Enum is an object whose keys are A and B, and whose properties are the elements of the enumeration. And typeof Enum is not Enum:

const element = Math.random() < 0.5 ? Enum.A : Enum.B;
// value element has type (Enum)
// type (Enum) is essentially the same as Enum.A | Enum.B
// which is a subtype of (0 | 1)

const enumObject = Enum;
// value enumObject has type (typeof Enum)
// type (typeof Enum) is essentially the same as { A: Enum.A; B: Enum.B }
// which is a subtype of { A: 0, B: 1 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment