TypeScript中的实用工具类型(Utility Types)

Posted 焱雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TypeScript中的实用工具类型(Utility Types)相关的知识,希望对你有一定的参考价值。

TypeScript中的实用工具类型是一些预定义的泛型类型,可用于操作或创建其它新类型。这些实用工具类型在所有TypeScript项目中都是全局可用的,因此无需添加任务依赖项即可使用它们。

1.Partial<Type>

将Type的所有属性都设置为可选的类型。

 interface Person 
   name: string;
    age: number;
   email: string;
 
 
  type PartialPerson = Partial<Person>;
 
  //相当于
  // interface Person 
  //   name?: string | undefined;
  //   age?: number | undefined;
  //   email?: string | undefined;
  // 
 
 interface Todo 
    title: string;
    description: string;
 
 
 function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) 
    return  ...todo, ...fieldsToUpdate ;
 
 
 const todo1 = 
   title: "organize desk",
   description: "clear clutter",
 ;
 
 const todo2 = updateTodo(todo1, 
    description: "throw out trash",
 );

2.Required<Type>

与Partical<Type> 相反,该类型由Type中所有属性设置为required组成。

 1 interface Person 
 2  name?: string | undefined;
 3  age?: number | undefined;
 4  email?: string | undefined;
 5 
 6 
 7 
 8 type RequiredPerson = Required<Person>;
 9 
10 // 相当于
11 // interface Person 
12 //   name: string;
13 //   age: number;
14 //   email: string;
15 // 

3.Omit<Type, Keys>

构建一个新类型--从类型 Type 中获取所有属性,然后从中剔除 Keys 属性。

 interface User 
   id: number;
   name: string;
   email: string;
   age: number;
 
 
 type UserWithoutEmail = Omit<User, \'email\'>;
 
 // 相当于
 // interface Person 
 //   id: string;
 //   name: string;
 //   age: number;
 // 

也可以移除多个属性,

 interface User 
   id: number;
   name: string;
   email: string;
   age: number;
 
 
 type UserWithoutEmailAndName = Omit<User, \'email\' | \'name\'>;
 
 // 相当于 
 // interface Person 
 //   id: string;
 //   age: number;
 // 

4.Pick<Type, Keys>

从类型 Type 中挑选部分属性 Keys 来构造类型,与Omit相反。

 interface User 
   id: number;
   name: string;
   email: string;
   age: number;
 
 
 type UserWithEmailAndName = Pick<User, \'email\' | \'name\'>;
 
 // 相当于
 // interface Person 
 //   name: string;
 //   email: string;
 // 

可以组合使用这些类型,创造新的类型

 interface User 
   id: number;
   name: string;
   email: string;
   age: number;
 
 
 type PartialPick = Partial<Pick<User, \'email\' | \'name\'>>;
 
 // 相当于
 // interface Person 
 //   name?: string | undefined;
 //   email?: string | undefined;
 // 
 interface User 
   id: number;
   name: string;
   email: string;
   age: number;
 
 
 type OmitPartialPick = Omit<Partial<Pick<User, \'email\' | \'name\'>>, \'email\'>;
 
 // 相当于 
 // interface Person 
 //   name?: string | undefined;
 // 

5.Readonly<Type>

通过该Type构造新类型,并将它所有的属性设置为只读的,也就意味着构造出的类型的属性不能被再次赋值。

 interface Person 
   id: number;
   name: string;
   age: number;
 
 
 type ReadonlyPerson = Readonly<Person>;
 
 //相当于
 // interface Person 
 //   readonly id: number;
 //   readonly name: string;
 //   readonly age: number;
 // 
 
 const person: ReadonlyPerson = 
   id: 1,
   name: \'John\',
   age: 25
 ;
 
 person.name = \'Mike\'; // Error: Cannot assign to \'name\' because it is a read-only property.

这个类型可用来表示在运行时会失败的赋值表达式(比如,当尝试给冻结对象的属性再次赋值时)

Object.freeze

 function freeze<T>(obj: T): Readonly<T>;

6.Record<Keys, Type>

构造一个对象类型,其属性为Keys,属性值为Type;该实用工具类型可用于将一种类型的属性映射到另一种类型。

 interface CatInfo 
   age: number;
   breed: string;
 
  
 type CatName = "miffy" | "boris" | "mordred";
  
 const cats: Record<CatName, CatInfo> = 
   miffy:  age: 10, breed: "Persian" ,
   boris:  age: 5, breed: "Maine Coon" ,
   mordred:  age: 16, breed: "British Shorthair" ,
 ;
  
 cats.boris;
  

7.Exclude<UnionType, ExcludedMembers>

通过从 UnionType 中排除所有可分配给 ExcludedMembers 的属性来构造一个类型;也就是删除 union 类型的成员来创建新类型。

 type T0 = Exclude<"a" | "b" | "c", "a">;
 type T0 = "b" | "c"
 
 type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
 type T1 = "c"
 
 type T2 = Exclude<string | number | (() => void), Function>;
 type T2 = string | number

8.Extract<Type, Union>

通过从 Type 中提取可分配给 Union 的所有联合成员来构造一个类型,与 Exclude 相反。

 type T0 = Extract<"a" | "b" | "c", "a" | "f">;
 type T0 = "a"
 
 type T1 = Extract<string | number | (() => void), Function>;
 type T1 = () => void

9.NonNullable<Type>

通过从 Type 中排除 null 和 undefined 来构造一个类型。

 type T0 = NonNullable<string | number | undefined>;
 type T0 = string | number
 
 type T1 = NonNullable<string[] | null | undefined>;
 type T1 = string[]

10.ReturnType<Type>

由函数类型 Type 的返回值类型构建一个新类型。

 function add(a: number, b: number): number 
   return a + b;
 
 
 type AddReturnType = ReturnType<typeof add>;
 // type AddReturnType = number;
 
 
 function addStr(a: string, b: string): string
   return a + b;
 
 
 type AddReturnType2 = ReturnType<typeof addStr>;
 // type AddReturnType2 = string;
 
 type T0 = ReturnType<() => string>;
 type T0 = string
 
 type T1 = ReturnType<(s: string) => void>;
 type T1 = void
 
 type T2 = ReturnType<<T>() => T>;    
 type T2 = unknown
 
 type T3 = ReturnType<<T extends U, U extends number[]>() => T>;
 type T3 = number[]

11.Parameters<Type>

由函数类型 Type 的参数类型来构建出一个元组类型。

 function add(a: number, b: string, c:boolean): string 
   return a + b;
 
 
 type AddReturnType = Parameters<typeof add>;
 // type AddReturnType = [a: number, b: string, c:boolean];
 
 type T0 = Parameters<() => string>;
 type T0 = []
 
 type T1 = Parameters<(s: string) => void>;
 type T1 = [s: string]
 
 type T2 = Parameters<<T>(arg: T) => T>;
 type T2 = [arg: unknown]

12.Awaited<Type>

这种类型旨在模拟异步函数中的 await 或 Promises 上的 .then() 方法等操作——具体来说,就是它们递归展开 Promises 的方式。

 async function fetchData(): Promise<string> 
   // fetch data from API and return a string
 
 
 type ResolvedResult = Awaited<ReturnType<typeof fetchData>>;
 // type ResolvedResult = string
 
 type A = Awaited<Promise<string>>;
 type A = string
  
 type B = Awaited<Promise<Promise<number>>>; 
 type B = number
  
 type C = Awaited<boolean | Promise<number>>;  
 type C = number | boolean

以上,是较常用的一些实用工具类型。

参考资料:

https://www.typescriptlang.org/docs/handbook/utility-types.html#uppercasestringtype

https://dev.to/arafat4693/typescript-utility-types-that-you-must-know-4m6k

 

从0开始的 TypeScriptの十四:内置工具类型

在之前的《从0开始的TypeScriptの十三》中,已经对typescript的工具类型中的关键字inferextendskeyoftypeofin这些有所了解了,那么接下来为了使用更加方便,可以对typescript中内置的工具类型进行一些学习。

工具类型名称描述用法
Readonly<T>将 T 中所有属性都变为只读Readonly< a: number > <===> readonly a: number
ReadonlyArray<T>返回一个 T 类型的只读数组ReadonlyArray<string> <===> readonly string[]
Partial<T>将 T 中所有的属性都变成可选类型Partial< a: number > <===> a?: number
Required<T>将 T 中所有的属性都变成必选类型和上面的Partial正好相反
Pick<T, K extends keyof T>从 T 中摘取部分属性Pick< a: number, b: string, c: boolean , 'a' | 'c'> <===> a: number, c: boolean
Omit<T, K extends keyof T>从 T 中排除部分属性Omit< a: number, b: string, c: boolean , 'a' | 'c'> <===> b: string
Exclude<T, U>从 T 中剔除可以赋值给 U 的类型Exclude<number | string | boolean, string> <===> number | boolean
Extract<T, U>提取 T 中可以赋值给 U 的类型Exclude<number | string | boolean, string> <===> string
Record<K, T>返回属性名为 K,属性值为 T 的类型Record<'a' | 'b', () => void> <===> a: ()=>void, b: ()=>void
NonNullable<T>从 T 中剔除 null 和 undefinedNonNullable<string | null | undefined> <===> string
ConstructorParameters<T>获取 T 的构造函数参数类型组成的元组Compare是类,构造函数constructor(a: number, b:number) ... ConstructorParameters<typeof Compare> <===> [ a: number, b: number]
InstanceType<T>由构造函数类型T的实例类型来构建一个新类型InstanceType<typeof Compare> <===> Compare
Parameters<T>获取函数参数类型组成的元组Parameters<(a: number, b: string) => number> <===> [ a: number, b: number]
ReturnType<T>获取函数返回值类型ReturnType<(a: number, b: string) => number> <===> number


上面这些内置的工具类型能够很大程度上简化代码。

比如说给定一个interface接口,需要将内部所有属性都变成可选类型

虽然我们自己也可以写,但是如果直接使用现有的内置工具类型Partial不是更好么

interface A 
    name: string,
    age: number,
    action: ()=>void

type PartialFun<T> = 
    [K in keyof T] ? : T[K]

type _A = PartialFun<A>

或者说我在网上看到的一道转换题目:

  • 如何定义一个SetOptional工具类型,支持把给定的keys对应的属性变成可选的?
type Foo = 
	a: number;
	b?: string;
	c: boolean;


// 测试用例
type SomeOptional = SetOptional<Foo, 'a' | 'b'>;

这样在修改时思考方式,需要对Fooa | b匹配的属性拆除来变成可选,然后不匹配的属性维持不变,最后将可选和不可选通过& 进行联合

// 对应的属性变成可选
type CommonFun<T, K> = 
    [ t in keyof T as t extends K ? t: never] ?: T[t]

// 不对应的属性
type Unequal<T, K> = 
    [ t in keyof T as t extends K ? never: t] : T[t]

type SetOptional<T, K> = CommonFun<T, K> & Unequal<T, K> 

不过这样虽然思路很清晰,但是看起来写了好多。事实上,可以使用PartialPick来代替CommonFun, 然后用Omit代替Unequal

只需要像下面的一句就可以解决,是不是缩减了很多代码

type SetOptional<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K> 

当然这些都是要基于对typescript内置类型比较熟悉的情况下,最好的方式就是多多去使用。就像当初最开始学习javascript时,对于Math.floorsplice这些方法也是多使用才能够熟悉起来。

当然上面的这些例子可能会觉得实用性不大,那么将数组类型扁平化总应该算有点实用性吧

[ number, [string], [ boolean, [ void, string ] ] ] 转换成 number | string | boolean | void 扁平化联合

这种例子很容易就会想到递归函数

解决方案:首先使用infer X[]将当前数组中的元素进行判断,如果不是数组则直接返回,否则元素重新进入SetOptional判断循环。

type ArrType = [ number, [string], [ boolean, [ void, string ] ] ]

type SetOptional<T> = T extends (infer X)[] ? SetOptional<X> : T
// string | number | boolean | void
type Res = SetOptional<ArrType>

以上是关于TypeScript中的实用工具类型(Utility Types)的主要内容,如果未能解决你的问题,请参考以下文章

从0开始的 TypeScriptの十四:内置工具类型

从0开始的 TypeScriptの十四:内置工具类型

TypeScript 中的基本数据类型

2 分钟知晓 typeScript 所有数据类型

编写TypeScript工具类型,你需要知道的知识

typescript入门