banner
 Sayyiku

Sayyiku

Chaos is a ladder
telegram
twitter

TypeScript 映射類型

只讀類型 Readonly#

定義:用於將 T 類型的所有屬性設置為只讀狀態。

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

用法:

interface Person {
  name: string
  age: number
}

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

// 會報錯:Cannot assign to 'name' because it is a read-only property
person.name = 'Lily'

readonly 只讀, 被 readonly 標記的屬性只能在聲明時或類的構造函數中賦值,之後將不可改(即只讀屬性)。

只讀數組 ReadonlyArray#

定義:用於將 T 類型的數組設置為只讀狀態。只能在數組初始化時為變量賦值,之後數組無法修改。

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

用法:

interface Person {
  name: string
}

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

// 會報錯:Property 'push' does not exist on type 'readonly Person[]'
// personList.push({ name: 'Lucy' })

// 但是內部元素如果是引用類型,元素自身是可以進行修改的
personList[0].name = 'Lily'

可選類型 Partial#

定義:用於將 T 類型的所有屬性設置為可選狀態,首先通過 keyof T,取出類型 T 的所有屬性,然後通過 in 操作符進行遍歷,最後在屬性後加上 ?,將屬性變為可選屬性。

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

用法:

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

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

// 和上面 Partial 效果一樣
const params: Pick<Organization, 'address'> = {
  address: '...new address',
}

必選類型 Required#

定義:和 Partial<T> 作用相反,用於將 T 類型的所有屬性設置為必選狀態,首先通過 keyof T,取出類型 T 的所有屬性, 然後通過 in 操作符進行遍歷,最後在屬性後的 ? 前加上 -,將屬性變為必選屬性。

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

用法:

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

// 使用 Required 映射後返回的新類型,name 和 age 都變成了必選屬性
// 會報錯:Type '{}' is missing the following properties from type 'Required<Person>': name, age
let person: Required<Person> = {}

提取屬性 Pick#

定義:從 T 類型中提取部分屬性,作為新的返回類型。

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

用法:

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#

定義:和 Pick 作用相反,用於從 T 類型中,排除部分屬性,然後返回一個新類型。

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

用法:

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

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

Omit 推導過程:

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

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

摘取類型 Extract<T,U>#

定義:提取 T 中可以賦值給 U 的類型。

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

用法:

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

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

排除類型 Exclude<T,U>#

定義:與 Extract 用法相反,從 T 中剔除可以賦值給 U 的類型。

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

用法:

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

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

屬性映射 Record<K,T>#

定義:接收兩個泛型,K 必須可以是可以賦值給 string | number | symbol 的類型,通過 in 操作符對 K 進行遍歷,每一個屬性的類型都必須是 T 類型。

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

Record 是 TypeScript 中一個很實用的範型類型。它需要兩個具體的參數類型,Record<K, V> 用於指定一個對象的類型。其中,對象的所有 key 都是 K 類型的,而這些 key 對應的值則都是 V 類型。如果不使用 Record 類型,可能需要用如下的方法來達到同等的效果:

type RecordExample = Record<string, number>

// 等價於
interface EquivalentExample {
  [key: string]: number
}

用法一:將 Person 類型的數組轉化成對象映射:

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
})

用法二:傳遞參數時,希望參數是一個對象,但是不確定具體的類型,就可以使用 Record 作為參數類型:

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

用法三:寫一個函數,可以將參數對象中所有的值都轉化成對應的數字,保證輸入和輸出的對象有相同的 key:

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>)
}

然而,需要注意的一點是,在使用聯合類型的時候 Record 本身也存在局限性(這一點本身是 TypeScript 的局限性)。還是以上面的 'apple' | 'banana' | 'orange' 為例,如果這麼寫,那麼下面的代碼將是錯誤的:

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

Record 天然並不能解決可選 key 的情況。Record<'A' | 'B', number> 的含義是 A 和 B 都需要是這個類型的 key,而不是說只需要有 A 或 B 一個做 key 就可以了。對於這種需要可選的情況,可以再套上一層 Partial 來滿足需求:

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

不可為空類型 NonNullable#

定義:從 T 中剔除 nullundefinednever 類型,不會剔除 voidunknow 類型。

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

用法:

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#

定義:返回 class 中構造函數參數類型組成的元組類型

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

用法:

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#

定義:獲取 class 構造函數的返回類型。

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

用法:

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 Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

用法:

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

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

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

函數返回值類型 ReturnType#

定義:獲取函數的返回值類型。

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

使用:

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

type FunctionReturnType = ReturnType<FunctionType> // boolean | string
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。