Skip to main content

TypeScript: Advanced Types

Mapped Types

📘 typescriptlang.org > mapped types

Mapped types allow you to create new types by transforming properties of an existing type. They are particularly useful for creating variations of a type, such as making all properties optional, readonly, or converting types of properties. Key Concepts

  • Mapping Over a Type's Properties: You can create a new type by iterating over the properties of an existing type.
  • Template Literals in Keys: Mapped types can use template literals to transform the keys.
  • Modifying Property Modifiers: You can make properties optional, required, or readonly using modifiers.

Syntax

type MappedType = {
[Property in keyof ExistingType]: TransformedType;
};

Example

Create a new type where all properties of an existing type are of type string.

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

type StringifiedPerson = {
[Key in keyof Person]: string;
};

// StringifiedPerson is equivalent to:
// {
// name: string;
// age: string;
// }

Create a new type where all properties are optional.

type Partial<T> = {
[P in keyof T]?: T[P];
};

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

type PartialPerson = Partial<Person>;

// PartialPerson is equivalent to:
// {
// name?: string;
// age?: number;
// }

Create a new type where all properties are readonly.

type Readonly<T> = {
[P in keyof T]: Readonly<T[P]>;
};

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

type ReadonlyPerson = Readonly<Person>;

// ReadonlyPerson is equivalent to:
// {
// readonly name: string;
// readonly age: number;
// }

Create a new type that removes readonly and ? modifiers from properties.

type Mutable<T> = {
-readonly [P in keyof T]-?: T[P];
};

type ReadonlyPerson = {
readonly name: string;
readonly age?: number;
};

type MutablePerson = Mutable<ReadonlyPerson>;

// MutablePerson is equivalent to:
// {
// name: string;
// age: number;
// }

Conditional Types

📘 typescriptlang.org > conditional types

Conditional types in TypeScript allow you to define types that depend on a condition, similar to ternary expressions in JavaScript. They enable you to create more flexible and expressive types by making decisions based on other types.

Syntax

T extends U ? X : Y

Samples

Basic Conditional Type

Create a type that evaluates to true if a type is string, otherwise false.

type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // true
type B = IsString<number>; // false

Filtering Types

Use conditional types to filter out null and undefined from a union type.

type NonNullable<T> = T extends null | undefined ? never : T;

type A = NonNullable<string | number | null | undefined>; // string | number

Inferring Types

Conditional types can infer types using the infer keyword.

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

function foo(): string {
return "hello";
}

type FooReturnType = ReturnType<typeof foo>; // string

Distributive Conditional Types

Conditional types distribute over union types. Each member of the union is evaluated individually.

type Example<T> = T extends string ? "string" : "other";

type A = Example<string | number>; // "string" | "other"

Conditional Type with Inference

Using infer to extract the element type from an array.

type ElementType<T> = T extends (infer U)[] ? U : T;

type A = ElementType<number[]>; // number
type B = ElementType<string[]>; // string
type C = ElementType<boolean>; // boolean

Conditional Type with Constraints

Using conditional types to create types based on constraints.

type ExtractString<T> = T extends string ? T : never;

type A = ExtractString<string | number | boolean>; // string

Utility Types Built with Conditional Types

Several TypeScript utility types are built using conditional types:

  • Exclude<T, U>: Excludes from T those types that are assignable to U.
  • Extract<T, U>: Extracts from T those types that are assignable to U.
  • NonNullable<T>: Excludes null and undefined from T.
  • ReturnType<T>: Extracts the return type of a function type T.
  • InstanceType<T>: Extracts the instance type of a constructor function type T.

Literal Types

📘 typescriptlang.org > literal types

Literal types in TypeScript allow you to specify exact values for a type. They are useful for defining types that accept only specific values, such as strings, numbers, or booleans.

type SecretToEverything = 42;

let secret: SecretToEverything = 42; // ok
let secret: SecretToEverything = 43; // error

Template Literal Types

📘 typescriptlang.org > template literal types

Template literal types in TypeScript allow you to create new types by concatenating string literals. They are useful for creating types that depend on other types or values.

type Name = `Mr. ${string}`;

let name: Name = `Mr. Smith`; // ok
let name: Name = `Mrs. Smith`; // error

Recursive Types

Recursive types in TypeScript are a way to define a type that references itself. Recursive types are used to define complex data structures, such as trees or linked lists, where a value can contain one or more values of the same type.

Example 1: Tree structure

A common use of recursive types is to define a tree structure where each node can have child nodes of the same type.

interface TreeNode {
value: number;
children?: TreeNode[];
}

// Example of a tree structure
const tree: TreeNode = {
value: 1,
children: [
{
value: 2,
children: [{ value: 4 }, { value: 5 }],
},
{
value: 3,
children: [{ value: 6 }, { value: 7 }],
},
],
};

console.log(tree);

Example 2: Linked list

Another example of recursive types is defining a linked list where each node points to the next node in the list.

interface ListNode<T> {
value: T;
next?: ListNode<T>;
}

// Example of a linked list
const list: ListNode<number> = {
value: 1,
next: {
value: 2,
next: {
value: 3,
next: {
value: 4,
},
},
},
};

console.log(list);

Automated Amazon Reports

Automatically download Amazon Seller and Advertising reports to a private database. View beautiful, on demand, exportable performance reports.

bidbear.io