Item 8: Know How to Tell Whether a Symbol Is in the Type Space or Value Space

May 10, 2024 ยท View on GitHub

Things to Remember

  • Know how to tell whether you're in type space or value space while reading a TypeScript expression. Use the TypeScript playground to build an intuition for this.
  • Every value has a static type, but this is only accessible in type space. Type space constructs such as type and interface are erased and are not accessible in value space.
  • Some constructs, such as class or enum, introduce both a type and a value.
  • typeof, this, and many other operators and keywords have different meanings in type space and value space.

Code Samples

interface Cylinder {
  radius: number;
  height: number;
}

const Cylinder = (radius: number, height: number) => ({radius, height});

๐Ÿ’ป playground


function calculateVolume(shape: unknown) {
  if (shape instanceof Cylinder) {
    shape.radius
    //    ~~~~~~ Property 'radius' does not exist on type '{}'
  }
}

๐Ÿ’ป playground


type T1 = 'string literal';
const v1 = 'string literal';
type T2 = 123;
const v2 = 123;

๐Ÿ’ป playground


interface Person {
  first: string;
  last: string;
}
const jane: Person = { first: 'Jane', last: 'Jacobs' };
//    โ€•โ€•โ€•โ€•           โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€• Values
//          โ€•โ€•โ€•โ€•โ€•โ€• Type

๐Ÿ’ป playground


function email(to: Person, subject: string, body: string): Response {
  //     โ€•โ€•โ€•โ€•โ€• โ€•โ€•          โ€•โ€•โ€•โ€•โ€•โ€•โ€•          โ€•โ€•โ€•โ€•                    Values
  //               โ€•โ€•โ€•โ€•โ€•โ€•           โ€•โ€•โ€•โ€•โ€•โ€•        โ€•โ€•โ€•โ€•โ€•โ€•   โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€• Types
  // ...
}

๐Ÿ’ป playground


class Cylinder {
  radius: number;
  height: number;
  constructor(radius: number, height: number) {
    this.radius = radius;
    this.height = height;
  }
}

function calculateVolume(shape: unknown) {
  if (shape instanceof Cylinder) {
    shape
    // ^? (parameter) shape: Cylinder
    shape.radius
    //    ^? (property) Cylinder.radius: number
  }
}

๐Ÿ’ป playground


type T1 = typeof jane;
//   ^? type T1 = Person
type T2 = typeof email;
//   ^? type T2 = (to: Person, subject: string, body: string) => Response

const v1 = typeof jane;  // Value is "object"
const v2 = typeof email;  // Value is "function"

๐Ÿ’ป playground


const first: Person['first'] = jane['first'];  // Or jane.first
//    โ€•โ€•โ€•โ€•โ€•                    โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€•โ€• Values
//           โ€•โ€•โ€•โ€•โ€•โ€• โ€•โ€•โ€•โ€•โ€•โ€•โ€•                  Types

๐Ÿ’ป playground


type PersonEl = Person['first' | 'last'];
//   ^? type PersonEl = string
type Tuple = [string, number, Date];
type TupleEl = Tuple[number];
//   ^? type TupleEl = string | number | Date

๐Ÿ’ป playground


function email(options: {to: Person, subject: string, body: string}) {
  // ...
}

๐Ÿ’ป playground


function email({
  to: Person,
  //  ~~~~~~ Binding element 'Person' implicitly has an 'any' type
  subject: string,
  //       ~~~~~~ Binding element 'string' implicitly has an 'any' type
  body: string
  //    ~~~~~~ Binding element 'string' implicitly has an 'any' type
}) { /* ... */ }

๐Ÿ’ป playground


function email(
  {to, subject, body}: {to: Person, subject: string, body: string}
) {
  // ...
}

๐Ÿ’ป playground