banner
 Sayyiku

Sayyiku

Chaos is a ladder
telegram
twitter

TypeScript Mapped Types

Readonly Type#

Definition: Used to set all properties of type T to read-only.

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

Usage:

interface Person {
  name: string
  age: number
}

const person: Readonly<Person> = {
  name: 'Lucy',
  age: 22,
}

// Will throw an error: Cannot assign to 'name' because it is a read-only property
person.name = 'Lily'

readonly is read-only, properties marked with readonly can only be assigned during declaration or in the class constructor, and cannot be modified afterwards (i.e., read-only properties).

ReadonlyArray#

Definition: Used to set an array of type T to read-only. Variables can only be assigned during array initialization, and the array cannot be modified afterwards.

interface ReadonlyArray<T> {
  [Symbol.iterator](): IterableIterator<T>
  entries(): IterableIterator<[number, T]>
  keys(): IterableIterator<number>
  values(): IterableIterator<T>
}

Usage:

interface Person {
  name: string
}

const personList: ReadonlyArray<Person> = [{ name: 'Jack' }, { name: 'Rose' }]

// Will throw an error: Property 'push' does not exist on type 'readonly Person[]'
// personList.push({ name: 'Lucy' })

// However, if the internal elements are reference types, the elements themselves can be modified
personList[0].name = 'Lily'

Partial Type#

Definition: Used to set all properties of type T to optional. First, it retrieves all properties of type T using keyof T, then iterates over them using the in operator, and finally adds ? after the properties to make them optional.

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

Usage:

interface Organization {
  id: number
  name: string
  address: string
  type: string
  nationality: string
}

const params: Partial<Organization> = {
  address: '...new address',
}

// Same effect as above Partial
const params: Pick<Organization, 'address'> = {
  address: '...new address',
}

Required Type#

Definition: The opposite of Partial<T>, used to set all properties of type T to required. First, it retrieves all properties of type T using keyof T, then iterates over them using the in operator, and finally adds - before the ? to make the properties required.

type Required<T> = {
  [P in keyof T]-?: T[P]
}

Usage:

interface Person {
  name?: string
  age?: number
}

// The new type returned after using Required mapping, name and age both become required properties
// Will throw an error: Type '{}' is missing the following properties from type 'Required<Person>': name, age
let person: Required<Person> = {}

Pick Type#

Definition: Extracts a subset of properties from type T to create a new return type.

type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}

Usage:

interface Goods {
  type: string
  goodsName: string
  price: number
}

// type RequestGoodsParams = {
//     goodsName: string;
//     price: number;
// }
type RequestGoodsParams = Pick<Goods, 'goodsName' | 'price'>

const params: RequestGoodsParams = {
  goodsName: '',
  price: 10,
}

Omit Type#

Definition: The opposite of Pick, used to exclude certain properties from type T and return a new type.

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

Usage:

interface Rectangular {
  length: number
  height: number
  width: number
}

// type Square = {
//     length: number;
// }
type Square = Omit<Rectangular, 'height' | 'width'>

Omit inference process:

type Person = {
  name: string
  age: string
  location: string
}

type PersonWithoutLocation = Omit<Person, 'location'>
// Inference
type PersonWithoutLocation = Pick<Person, Exclude<'name' | 'age' | 'location', 'location'>>
// Inference
type PersonWithoutLocation = Pick<
  Person,
  ('name' extends 'location' ? never : 'name') | ('age' extends 'location' ? never : 'age') | ('location' extends 'location' ? never : 'location')
>
// Inference
type PersonWithoutLocation = Pick<Person, 'name' | 'age' | never>
// Inference
type PersonWithoutLocation = Pick<Person, 'name' | 'age'>
// Inference
type PersonWithoutLocation = {
  [p in 'name' | 'age']: Person[p]
}
// Inference
type PersonWithoutLocation = {
  name: string
  age: string
}

Extract Type Extract<T,U>#

Definition: Extracts types from T that can be assigned to U.

type Extract<T, U> = T extends U ? T : never

Usage:

type T01 = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'> // 'a' | 'c'

type T02 = Extract<string | number | (() => void), Function> // () => void

Exclude Type Exclude<T,U>#

Definition: The opposite of Extract, used to remove types from T that can be assigned to U.

type Exclude<T, U> = T extends U ? never : T

Usage:

type T00 = Exclude<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'> // 'b' | 'd'

type T01 = Exclude<string | number | (() => void), Function> // string | number

Record<K,T>#

Definition: Accepts two generics, where K must be a type that can be assigned to string | number | symbol. It iterates over K using the in operator, and each property's type must be of type T.

type Record<K extends string | number | symbol, T> = {
  [P in K]: T
}

Record is a very useful generic type in TypeScript. It requires two specific parameter types, Record<K, V> is used to specify the type of an object. All keys of the object are of type K, and the values corresponding to these keys are all of type V. If Record type is not used, the following method may be needed to achieve the same effect:

type RecordExample = Record<string, number>

// Equivalent to
interface EquivalentExample {
  [key: string]: number
}

Usage 1: Convert an array of Person type into an object mapping:

interface Person {
  name: string
  age: number
}

const personList = [
  { name: 'Jack', age: 26 },
  { name: 'Lucy', age: 22 },
  { name: 'Rose', age: 18 },
]

const personMap: Record<string, Person> = {}

personList.forEach((person) => {
  personMap[person.name] = person
})

Usage 2: When passing parameters, if you want the parameter to be an object but are unsure of the specific type, you can use Record as the parameter type:

function doSomething(obj: Record<string, any>) {}

Usage 3: Write a function that converts all values in the parameter object to the corresponding numbers, ensuring that the input and output objects have the same keys:

type Input = Record<string, string>
function transform<T extends Input>(input: T): Record<keyof T, number> {
  const keys: (keyof T)[] = Object.keys(input)
  return keys.reduce((acc, key) => {
    acc[key] = +input[key]
    return acc
  }, {} as Record<keyof T, number>)
}

However, it is important to note that when using union types, Record itself also has limitations (this is a limitation of TypeScript itself). For example, with 'apple' | 'banana' | 'orange', if written this way, the following code will be incorrect:

type Fruit = 'apple' | 'banana' | 'orange'
type Price = Record<Fruit, number>
// type error
const prices: Price = {
  apple: 20,
}

Record does not inherently solve the case of optional keys. Record<'A' | 'B', number> means that both A and B need to be keys of this type, not that only one of A or B can be a key. For such optional cases, you can wrap it with Partial to meet the requirement:

type Price = Partial<Record<Fruit, number>>
// correct
const prices: Price = {
  apple: 20,
}

NonNullable Type#

Definition: Removes null, undefined, and never types from T, but does not remove void or unknown types.

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

Usage:

type T01 = NonNullable<string | number | undefined> // string | number

type T02 = NonNullable<(() => string) | string[] | null | undefined> // (() => string) | string[]

type T03 = NonNullable<{ name?: string; age: number } | string[] | null | undefined> // {name?: string, age: number} | string[]

ConstructorParameters Type#

Definition: Returns a tuple type composed of the parameter types of the constructor in a class.

type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never

Usage:

class Person {
  name: string
  age: number
  gender: 'man' | 'women'

  constructor(name: string, age: number, gender: 'man' | 'women') {
    this.name = name
    this.age = age
    this.gender = gender
  }
}

type ConstructorType = ConstructorParameters<typeof Person> //  [name: string, age: number, gender: 'man' | 'women']

const params: ConstructorType = ['Jack', 20, 'man']

InstanceType#

Definition: Gets the return type of a class constructor.

type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any

Usage:

class Person {
  name: string
  age: number
  gender: 'man' | 'women'

  constructor(name: string, age: number, gender: 'man' | 'women') {
    this.name = name
    this.age = age
    this.gender = gender
  }
}

type Instance = InstanceType<typeof Person> // Person

const params: Instance = {
  name: 'Jack',
  age: 20,
  gender: 'man',
}

Parameters Type#

Definition: Gets a tuple type composed of the parameter types of a function.

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

Usage:

type FunctionType = (name: string, age: number) => boolean

type FunctionParamsType = Parameters<FunctionType> // [name: string, age: number]

const params: FunctionParamsType = ['Jack', 20]

ReturnType Type#

Definition: Gets the return type of a function.

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

Usage:

type FunctionType = (name: string, age: number) => boolean | string

type FunctionReturnType = ReturnType<FunctionType> // boolean | string
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.