type-value-space

📁 marius-townhouse/effective-typescript-skills 📅 10 days ago
1
总安装量
1
周安装量
#42087
全站排名
安装命令
npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill type-value-space

Agent 安装分布

mcpjam 1
openhands 1
replit 1
windsurf 1
zencoder 1

Skill 文档

Know How to Tell Whether a Symbol Is in Type Space or Value Space

Overview

Every symbol in TypeScript exists in type space, value space, or both.

The same name can refer to completely different things depending on context. Understanding this distinction is essential for reading TypeScript code correctly.

When to Use This Skill

  • Debugging confusing type errors
  • Understanding class vs interface behavior
  • Using typeof correctly
  • Destructuring with type annotations
  • Understanding enum behavior

The Iron Rule

ALWAYS determine whether you're in type space or value space before interpreting a symbol.

Remember:

  • Types are erased at runtime (use TypeScript Playground to verify)
  • Classes introduce BOTH a type AND a value
  • typeof means different things in each space
  • Destructuring syntax differs from type annotation syntax

Detection: Same Name, Different Meaning

// Same name, different entities!
interface Cylinder {
  radius: number;
  height: number;
}
const Cylinder = (radius: number, height: number) => ({ radius, height });

// This refers to the VALUE (function), not the TYPE (interface)
if (shape instanceof Cylinder) {
  // instanceof is a runtime operator - uses value space
}

The Two Spaces

Type Space (Erased at Runtime)

type StringAlias = string;     // Type alias
interface Person { name: string; }  // Interface
type T = typeof myVar;         // Type-level typeof

Value Space (Exists at Runtime)

const x = 123;                 // Variable
function add(a, b) { return a + b; }  // Function
const t = typeof myVar;        // Runtime typeof (returns string)

Both Spaces (class and enum)

class MyClass {
  value: number = 0;
}

// As a type: describes the shape of instances
const instance: MyClass = new MyClass();

// As a value: the constructor function
const ctor = MyClass;  // typeof ctor is typeof MyClass

Context Determines Space

After : or as = Type Space

const alice: Person = { name: 'Alice' };
//           ^^^^^^ Type space

const bob = someValue as Person;
//                       ^^^^^^ Type space

After = = Value Space

const x = Person;  // Value space (error if Person is only a type)

Function Signatures Alternate

function email(to: Person, subject: string, body: string): Response {
//             ^^          ^^^^^^^          ^^^^           ^^^^^^^^ Types
//       ^^^^^^^ ^^        ^^^^^^^ ^^^^^^   ^^^^ ^^^^              Values
}

typeof: Different in Each Space

Type Space typeof

const person = { name: 'Alice', age: 30 };

type PersonType = typeof person;
//   ^? type PersonType = { name: string; age: number }

// Gets the TypeScript type of a value

Value Space typeof

const person = { name: 'Alice', age: 30 };

const t = typeof person;  // "object"
// JavaScript's runtime typeof - only 8 possible values:
// "string", "number", "boolean", "undefined", 
// "object", "function", "symbol", "bigint"

Other Dual-Meaning Constructs

Construct Type Space Value Space
typeof TypeScript type of value JS runtime type (8 options)
this Type of this JS this keyword
&, | Intersection, union Bitwise AND, OR
const as const context Variable declaration
extends Subtype/constraint Subclass
in Mapped types for...in loops
! Non-null assertion Logical NOT

Property Access: [] vs .

interface Person {
  first: string;
  last: string;
}

// Value space: both work
const name1 = person['first'];
const name2 = person.first;

// Type space: ONLY brackets work
type First = Person['first'];     // OK: string
type First2 = Person.first;       // Error!

Common Mistake: Destructuring with Types

Wrong: Type Names in Value Position

function email({
  to: Person,      // Error! 'Person' implicitly has 'any' type
  subject: string, // Error! 'string' implicitly has 'any' type
}) { }
// This creates variables named Person and string!

Right: Separate Types from Destructuring

function email(
  { to, subject, body }: { to: Person; subject: string; body: string }
) { }
// Destructures values, then annotates the whole parameter

Using the TypeScript Playground

The Playground shows what’s erased:

interface Person { name: string; }  // Disappears in JS
type StringType = string;           // Disappears in JS

class MyClass { }                   // Stays (has runtime value)
const x = 42;                       // Stays (has runtime value)

If a symbol disappears in the JS output, it was in type space only.

Pressure Resistance Protocol

1. “instanceof Should Work with Interfaces”

Pressure: “Why doesn’t x instanceof MyInterface work?”

Response: instanceof is a runtime operator. Interfaces don’t exist at runtime.

Action: Use type guards or discriminated unions instead.

2. “typeof Should Give Me the Full Type”

Pressure: “Why is typeof x just ‘object’?”

Response: You’re using value-space typeof. For the TypeScript type, use it in a type annotation.

Action: type T = typeof x for the full TypeScript type.

Red Flags – STOP and Reconsider

  • instanceof with an interface or type alias
  • Expecting runtime typeof to distinguish object shapes
  • Destructuring that creates unexpected variable names
  • Type errors mentioning “cannot be used as a value”

Common Rationalizations (All Invalid)

Excuse Reality
“Class and interface are the same” Classes have runtime values; interfaces don’t
“typeof works the same everywhere” Completely different meaning in type vs value space
“I can destructure with type names” Destructuring uses value space; annotations use type space

Quick Reference

// Type space indicators:
type X = ...       // After 'type'
interface X { }    // After 'interface'
x: Type            // After ':'
as Type            // After 'as'

// Value space indicators:
const x = ...      // After '='
x instanceof Y     // instanceof operand
typeof x           // Without type annotation

// Both spaces:
class X { }        // Introduces type and value
enum X { }         // Introduces type and value

The Bottom Line

Context determines whether a symbol refers to a type or a value.

The same name can mean completely different things. When code is confusing, first determine which space you’re in. Use the TypeScript Playground to see what gets erased – that’s your type space.

Reference

Based on “Effective TypeScript” by Dan Vanderkam, Item 8: Know How to Tell Whether a Symbol Is in the Type Space or Value Space.