Type Manipulation

Back

Loading concept...

πŸ”§ TypeScript Type Manipulation: Your Magic Toolbox

Imagine you have a magical toolbox. Inside are special tools that can look at any toy, take it apart, and build new toys from the pieces. That’s what TypeScript’s type manipulation tools do with your code!


πŸ—οΈ The keyof Operator β€” Finding All the Keys

Think of an object like a treasure chest with labeled compartments. The keyof operator is like asking: β€œWhat are all the labels on this chest?”

type Person = {
  name: string;
  age: number;
  email: string;
};

// "What labels does Person have?"
type PersonKeys = keyof Person;
// Result: "name" | "age" | "email"

Why Is This Useful?

When you want to make sure someone only uses real keys from an object:

function getValue<T, K extends keyof T>(
  obj: T,
  key: K
): T[K] {
  return obj[key];
}

const person = { name: "Sam", age: 10 };
getValue(person, "name"); // βœ… Works!
getValue(person, "color"); // ❌ Error!

πŸ’‘ Remember: keyof gives you a list of all property names as a union type.


πŸ” The typeof Operator β€” Copying a Type from a Value

Sometimes you already have a real thing and want to create a type that matches it exactly. typeof is like taking a photo of something and using that photo as a blueprint!

const robot = {
  name: "Beep",
  batteryLevel: 100,
  isActive: true
};

// Create a type from the robot
type Robot = typeof robot;
// Result: { name: string;
//           batteryLevel: number;
//           isActive: boolean }

Combining Powers

const colors = {
  red: "#ff0000",
  green: "#00ff00",
  blue: "#0000ff"
};

type ColorName = keyof typeof colors;
// Result: "red" | "green" | "blue"

πŸ’‘ Remember: typeof extracts a type from an existing value. It’s like reverse engineering!


πŸ“¦ Indexed Access Types β€” Reaching Inside

Imagine you have a toy box with labeled sections. Indexed access lets you ask: β€œWhat kind of toy is in the β€˜cars’ section?”

type Toy = {
  cars: string[];
  dolls: number;
  games: { name: string };
};

type CarsType = Toy["cars"];
// Result: string[]

type GamesType = Toy["games"];
// Result: { name: string }

Going Deeper

You can chain these to dig into nested structures:

type GameName = Toy["games"]["name"];
// Result: string

Using with keyof

type AnyToyType = Toy[keyof Toy];
// Result: string[] | number | { name: string }

πŸ’‘ Remember: Use square brackets [] with a type to look up what’s inside that property.


πŸ”€ Conditional Types β€” Making Decisions

What if your type could think and make choices? Conditional types are like asking: β€œIf this is true, give me A. Otherwise, give me B.”

type IsString<T> = T extends string
  ? "Yes, it's a string!"
  : "Nope, not a string";

type Test1 = IsString<"hello">;
// Result: "Yes, it's a string!"

type Test2 = IsString<42>;
// Result: "Nope, not a string"

Real-World Example

type Flatten<T> = T extends any[]
  ? T[number]
  : T;

type A = Flatten<string[]>;
// Result: string

type B = Flatten<number>;
// Result: number

πŸ’‘ Remember: T extends U ? X : Y means β€œIs T a kind of U? If yes, use X. If no, use Y.”


🎣 The infer Keyword β€” Catching Types

The infer keyword is like a fishing net that catches types as they pass by. You use it inside conditional types to extract a piece of a type.

type GetReturnType<T> = T extends
  (...args: any[]) => infer R
    ? R
    : never;

type A = GetReturnType<() => string>;
// Result: string

type B = GetReturnType<() => number[]>;
// Result: number[]

Another Example: Array Elements

type ElementOf<T> = T extends (infer E)[]
  ? E
  : never;

type X = ElementOf<string[]>;
// Result: string

type Y = ElementOf<number[]>;
// Result: number

πŸ’‘ Remember: infer creates a temporary variable to capture part of a type during pattern matching.


🌊 Distributive Conditional Types β€” One at a Time

When you pass a union type to a conditional type, something magical happens. TypeScript checks each member of the union separately!

type ToArray<T> = T extends any
  ? T[]
  : never;

type Result = ToArray<string | number>;
// Checks: string β†’ string[]
// Checks: number β†’ number[]
// Result: string[] | number[]

Distribution in Action

graph TD A["ToArray&lt;string &#124; number&gt;"] --> B["string &#124; number"] B --> C["Check: string"] B --> D["Check: number"] C --> E["string[]"] D --> F["number[]"] E --> G["string[] &#124; number[]"] F --> G

Preventing Distribution

Sometimes you don’t want this behavior. Wrap in brackets:

type ToArrayNoDistribute<T> = [T] extends [any]
  ? T[]
  : never;

type Result = ToArrayNoDistribute<string | number>;
// Result: (string | number)[]

πŸ’‘ Remember: Conditional types distribute over unions by default. Use [T] to prevent this.


πŸ”„ Recursive Conditional Types β€” Types That Call Themselves

What if a type needs to keep working on itself, layer by layer? Like peeling an onion πŸ§…!

type DeepFlatten<T> = T extends any[]
  ? DeepFlatten<T[number]>
  : T;

type A = DeepFlatten<string[]>;
// Result: string

type B = DeepFlatten<number[][]>;
// Result: number

type C = DeepFlatten<boolean[][][]>;
// Result: boolean

Building Deep Readonly

type DeepReadonly<T> = T extends object
  ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
  : T;

type Original = {
  a: { b: { c: string } }
};

type Frozen = DeepReadonly<Original>;
// All levels are now readonly!

πŸ’‘ Remember: Recursive types reference themselves to handle nested structures.


πŸ›‘οΈ The NoInfer Utility Type β€” Stopping Inference

Sometimes TypeScript tries too hard to guess types. NoInfer tells TypeScript: β€œDon’t guess here! Use what I already told you.”

function createPair<T>(
  value: T,
  defaultValue: NoInfer<T>
) {
  return { value, defaultValue };
}

// Without NoInfer, TS might get confused
createPair("hello", "world"); // βœ… Both strings

// This helps prevent accidental widening
createPair(1, 2); // βœ… Both numbers

When to Use It

Use NoInfer when you have multiple parameters of the same type and want one of them to not influence how TypeScript guesses the type.

function choose<T>(
  items: T[],
  fallback: NoInfer<T>
): T {
  return items[0] ?? fallback;
}

choose([1, 2, 3], 0); // βœ… T is number
choose(["a", "b"], "default"); // βœ… T is string

πŸ’‘ Remember: NoInfer<T> makes that parameter β€œinvisible” during type inference.


🎯 Quick Reference Table

Tool What It Does Example
keyof Gets all property names keyof Person β†’ "name" | "age"
typeof Extracts type from value typeof obj β†’ { a: number }
T["key"] Looks up property type Person["name"] β†’ string
T extends U ? X : Y Makes decisions IsString<5> β†’ "no"
infer Captures types GetReturn<fn> β†’ return type
Distributive Checks union members separately ToArray<A | B> β†’ A[] | B[]
Recursive Types calling themselves DeepFlatten<T[][]>
NoInfer Prevents inference fn(x: NoInfer<T>)

🌟 Putting It All Together

Here’s a real example using multiple tools:

type API = {
  getUser: (id: number) => { name: string };
  getPosts: () => { title: string }[];
};

// Get all method names
type Methods = keyof API;
// "getUser" | "getPosts"

// Get return type of getUser
type UserResponse = ReturnType<API["getUser"]>;
// { name: string }

// Flatten the posts array
type Post = API["getPosts"] extends
  () => (infer P)[] ? P : never;
// { title: string }

🎈 You Did It!

You now have a magical toolbox with:

  • πŸ—οΈ keyof β€” to find all the keys
  • πŸ” typeof β€” to copy types from values
  • πŸ“¦ Indexed Access β€” to reach inside
  • πŸ”€ Conditional Types β€” to make decisions
  • 🎣 infer β€” to catch hidden types
  • 🌊 Distributive Types β€” to work on unions one by one
  • πŸ”„ Recursive Types β€” to handle deep nesting
  • πŸ›‘οΈ NoInfer β€” to control inference

With these tools, you can transform, reshape, and create new types like magic! πŸͺ„

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.