学习TS高阶类型

364 阅读1分钟

高阶类型

TypeScript提供了实用程序类型来促进常见的类型转换。

基本都是照着官方的英文文档读下来的,记录下来方便以后自己回顾。

Awaited<Type>

版本:TypeScript 4.5+

这种类型的目的是为 async 函数中的 await 或 Promises上的 .then() 方法之类的操作建模--具体来说,就是它们递归展开 Promises的方式。

async function getData(): Promise<number> {  
// 模拟一个长时间运行的操作  
await new Promise(resolve => setTimeout(resolve, 5000));  
return 42;  
}  
async function printData(): Promise<void> {  
console.log("Starting printData");  
// 等待 getData 完成后再继续执行  
const data: Awaited<number> = await getData();  
console.log(`Data received: ${data}`);  
}  

Partial<Type>

版本:TypeScript 2.1+

构造一个类型,其中 Type 的所有属性都设置为可选。此实用程序将返回一个表示给定类型的所有子集的类型。

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",  
});  

Required<Type>

版本:TypeScript 2.8+

构造一个由 Type 的所有属性组成的类型,该属性设置为required。与 Partial 相反。

interface Props {  
a?: number;  
b?: string;  
}  
const obj: Props = { a: 5 };  
  
const obj2: Required<Props> = { a: 5 }; // 错误提示: 初始值设定项类型 {a: number} 不可分配给变量类型 Required<Props>  
// 正确  
const obj3: Required<Props> = {a: 1, b: '2'}  
</script>  

Readonly<Type>

构造一个类型,其中 Type 的所有属性都设置为 readonly ,这意味着不能重新分配构造类型的属性。

版本:TypeScript 2.1+

<script setup lang="ts">  
interface Todo {  
title: string;  
}  
  
const todo: Readonly<Todo> = {  
title: "Delete inactive users",  
};  
todo.title = "Hello"; // 报错提示:尝试分配给常量或只读变量  

Record<Keys, Type>

构造属性键为 Keys 且属性值为 Type 的对象类型。此实用工具可用于将一个类型的属性映射到另一个类型。

版本:TypeScript 2.1+

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.  
* -miffy  
* -boris  
* -mordred  
*/  

Pick<Type, Keys>

通过从 Type 中选取属性集 Keys (字符串文字或字符串文字的联合)来构造类型。

版本:TypeScript 2.1+

interface Todo {  
title: string;  
description: string;  
completed: boolean;  
}  
  
type TodoPreview = Pick<Todo, "title" | "completed">;  
  
const todo: TodoPreview = {  
title: "Clean room",  
completed: false,  
};  
/**  
* todo.  
* -title  
* -completed  
*/  

Omit<Type, Keys>

通过从 Type 中选取所有属性,然后删除 Keys (字符串文字或字符串文字的联合)来构造类型。与 Pick 相反。

版本:TypeScript 3.5+

interface Todo {  
title: string;  
description: string;  
completed: boolean;  
createdAt: number;  
}  
  
type TodoPreview = Omit<Todo, "description">;  
  
const todo: TodoPreview = {  
title: "Clean room",  
completed: false,  
createdAt: 1615544252770,  
};  
/**  
* todo.  
* -title  
* -completed  
* -createdAt  
*/  
  
type TodoInfo = Omit<Todo, "completed" | "createdAt">;  
  
const todoInfo: TodoInfo = {  
title: "Pick up kids",  
description: "Kindergarten closes at 5pm",  
};  
/**  
* todoInfo.  
* -title  
* -description  
*/  

Exclude<UnionType, ExcludedMembers>

通过从 UnionType 中排除所有可分配给 ExcludedMembers 的联合成员来构造类型。在实际的 TypeScript 代码中, Exclude类型工具通常用于过滤掉不需要的类型, 例如从函数参数类型中排除某些类型, 或从对象属性类型中排除某些类型。

版本:TypeScript 2.8+

type MyUnion = 'foo' | 'bar' | 123 | true;  
// 从 MyUnion 中排除 'foo' 和 123  
type MyExcluded = Exclude<MyUnion, 'foo' | 123>;  
// MyExcluded 的类型为 'bar' | true  

Extract<Type, Union>

通过从 Type 中提取所有可分配给 Union 的联合成员来构造类型。在实际的 TypeScript 代码中, Extract 类型工具通常用于从联合类型中提取需要的成员类型,例如从函数返回值类型中提取某些类型。

版本:TypeScript 2.8+

type MyUnion = 'foo' | 'bar' | 123 | true;  
// 从 MyUnion 中提取字符串字面量类型  
type MyExtracted = Extract<MyUnion, string>;  
// MyExtracted 的类型为 'foo' | 'bar'  

NonNullable<Type>

通过从 Type 中排除 null 和 undefined 来构造类型。在实际的 TypeScript 代码中, NonNullable 类型工具通常用于确保变量或函数的返回值不为 null 或 undefined ,从而避免在运行时出现错误

版本:TypeScript 2.8+

type T0 = NonNullable<string | number | undefined>; // type T0 = string | number  
type T1 = NonNullable<string[] | null | undefined>; // type T1 = string[]  
type MyType = string | null | undefined;  
// 从 MyType 中排除 null 和 undefined 类型  
type MyNonNullable = NonNullable<MyType>;  
// MyNonNullable 的类型为 string  

Parameters<Type>

从函数类型 Type 的参数中使用的类型构造元组类型。在实际的 TypeScript 代码中, Parameters 类型工具通常用于处理函数参数类型,例如将函数参数类型转换为元组类型或从函数参数类型中提取某些类型。

版本:TypeScript 3.1+

type MyFunction = (x: number, y: string) => boolean;  
// 提取 MyFunction 的参数类型元组类型  
type MyParameters = Parameters<MyFunction>;  
// MyParameters 的类型为 [number, string]  
<script lang='ts' setup>  
import { ref } from 'vue'  
function add(a: number, b: number) {  
return a + b  
}  
const count = ref(0)  
function increment(delta: number) {  
count.value += delta  
}  
type AddParams = Parameters<typeof add>  
type IncrementParams = Parameters<typeof increment>  
const addParams: AddParams = [1, 2]  
const incrementParams: IncrementParams = [10]  
</script>  

ConstructorParameters<Type>

从构造函数类型的类型构造元组或数组类型。它生成一个包含所有参数类型的元组类型(如果 Type 不是函数,则生成类型 never )。在实际的 TypeScript 代码中, ConstructorParameters 类型工具通常用于处理类的构造函数参数类型,例如将构造函数参数类型转换为元组类型或从构造函数参数类型中提取某些类型。

版本:TypeScript 3.1+

class Person {  
constructor(public name: string, public age: number) {}  
}  
type PersonConstructorParams = ConstructorParameters<typeof Person>;  
// PersonConstructorParams 的类型为 [string, number]  

::: tip
ConstructorParameters<Type> 类型工具只适用于类的构造函数类型。因此,只有当你有一个类的构造函数类型时,才能使用 ConstructorParameters<Type> 类型工具来获取构造函数的参数类型元组类型。如果你没有类的构造函数类型,那么就不能使用 ConstructorParameters<Type> 类型工具来获取构造函数的参数类型元组类型。
:::

InstanceType<Type>

构造一个由 Type 中构造函数的实例类型组成的类型。在实际的 TypeScript 代码中, InstanceType 类型工具通常用于处理类的实例类型,例如将构造函数的实例类型转换为其他类型或从构造函数的实例类型中提取某些属性。

版本:TypeScript 2.8+

class Person {  
constructor(public name: string, public age: number) {}  
}  
type PersonInstanceType = InstanceType<typeof Person>;  
// PersonInstanceType 的类型为 Person  
// ChildComponent.vue  
<template>  
<div>{{ message }}</div>  
</template>  
<script lang="ts">  
import { defineComponent, ref } from 'vue'  
export default defineComponent({  
name: 'ChildComponent',  
props: {  
message: String  
},  
setup(props) {  
const messageRef = ref(props.message)  
return {  
message: messageRef  
}  
},  
methods: {  
logMessage() {  
console.log(this.message)  
}  
}  
})  
</script>  
// ParentComponent.vue  
<template>  
<div>  
<ChildComponent ref="childComponent" message="Hello from child component" />  
<button @click="logChildMessage">Log child message</button>  
</div>  
</template>  
<script lang="ts" setup>  
import { ref, onMounted } from 'vue'  
import ChildComponent from './ChildComponent.vue'  
const childComponentRef = ref<InstanceType<typeof ChildComponent> | null>(null)  
onMounted(() => {  
childComponentRef.value?.logMessage()  
})  
function logChildMessage() {  
childComponentRef.value?.logMessage()  
}  
</script>  

ThisParameterType<Type>

提取函数类型的this参数的类型,如果函数类型没有 this 参数,则为未知。

版本:TypeScript 3.3+

function toHex(this: Number) {  
return this.toString(16); // this: Number  
}  
  
function numberToString(n: ThisParameterType<typeof toHex>) {  
return toHex.apply(n);  
}  
<template>  
<div>{{ count }}</div>  
</template>  
<script lang="ts" setup>  
import { ref } from 'vue'  
const count = ref(0)  
function increment(this: ThisParameterType<typeof MyComponent>) {  
this.count++  
}  
export default {  
name: 'MyComponent',  
setup() {  
return {  
count,  
increment: increment.bind(this)  
}  
}  
}  
</script>  

OmitThisParameter<Type>

从 Type 中删除 this 参数。如果 Type 没有显式声明的 this 参数,则结果只是 Type 。否则,从 Type 创建一个没有 this 参数的新函数类型。泛型将被擦除,只有最后一个重载签名传播到新函数类型中。

版本:TypeScript 3.3+

function toHex(this: Number) {  
return this.toString(16);  
}  
  
const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5);  
  
console.log(fiveToHex());  

看不懂?请看下面的对比示例:

假设有一个函数类型 MyFunction ,它接受一个 this 参数和两个普通参数,返回值类型为 string

type MyFunction = (this: any, arg1: number, arg2: string) => string;  

现在,我们想要创建一个新的函数类型 MyNewFunction ,它与 MyFunction 类型相同,但省略了 this 参数。我们可以使用 OmitThisParameter<Type> 工具类型来实现这个目标

type MyNewFunction = OmitThisParameter<MyFunction>;  

这个新类型 MyNewFunction 的定义如下

type MyNewFunction = (arg1: number, arg2: string) => string;  

可以看到, MyNewFunction 类型与 MyFunction 类型相同,只是省略了 this 参数。

这个示例展示了 OmitThisParameter<Type> 工具类型的作用,它可以从函数类型中省略 this 参数,使得函数类型更加简洁和易于使用。

ThisType<Type>

此实用工具不返回转换后的类型。相反,它充当上下文 this 类型的标记。请注意,必须启用 noImplicitThis 标志才能使用此实用程序。

版本:TypeScript 2.3+

type ObjectDescriptor<D, M> = {  
data?: D;  
methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M  
};  
  
function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {  
let data: object = desc.data || {};  
let methods: object = desc.methods || {};  
return { ...data, ...methods } as D & M;  
}  
  
let obj = makeObject({  
data: { x: 0, y: 0 },  
methods: {  
moveBy(dx: number, dy: number) {  
this.x += dx; // Strongly typed this  
this.y += dy; // Strongly typed this  
},  
},  
});  
  
obj.x = 10;  
obj.y = 20;  
obj.moveBy(5, 5);  

在上面的例子中, makeObject 的参数中的 methods 对象具有包括 ThisType<D & M> 的上下文类型,因此 methods 对象内的方法中的this的类型是{ x: number, y: number } & { moveBy(dx: number, dy: number): void }。注意 methods 属性的类型如何同时是方法中 this 类型的推断目标和源。

ThisType<T> 标记接口只是在 lib.d.ts 中声明的一个空接口。除了在对象文字的上下文类型中被识别之外,接口的行为就像任何空接口一样。
::: tip
再来一个简单的示例来帮助理解它的作用
:::
假设有一个对象类型 MyObject ,它有一个属性 name 和一个方法 getName ,该方法返回对象的 name 属性值:

type MyObject = {  
name: string;  
getName(): string;  
};  

现在,我们想要创建一个新的对象类型 MyNewObject ,它与 MyObject 类型相同,但在 getName 方法中使用 this 关键字来引用对象本身。我们可以使用 ThisType<Type> 工具类型来实现这个目标

type MyNewObject = ThisType<{ name: string }> & {  
getName(this: MyNewObject): string;  
};  

这个新类型 MyNewObject 的定义如下:

type MyNewObject = {  
name: string;  
getName(this: MyNewObject): string;  
}  

可以看到, MyNewObject 类型与 MyObject 类型相同,只是在 getName 方法中使用了 this 关键字来引用对象本身。这是通过使用 ThisType<Type> 工具类型来实现的,其中 Type 参数指定了 this 关键字的类型。

Uppercase<StringType>

将字符串中的每个字符转换为大写形式。

版本:TypeScript 2.8+

type Greeting = "Hello, world"  
type ShoutyGreeting = Uppercase<Greeting>  
  
type ShoutyGreeting = "HELLO, WORLD"  
  
type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`  
type MainID = ASCIICacheKey<"my_app"> // type MainID = "ID-MY_APP"  

Lowercase<StringType>

将字符串中的每个字符转换为小写等效字符。

版本:TypeScript 2.8+

type Greeting = "Hello, world"  
type QuietGreeting = Lowercase<Greeting>  
  
type QuietGreeting = "hello, world"  
  
type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`  
type MainID = ASCIICacheKey<"MY_APP"> // type MainID = "id-my_app"  

Capitalize<StringType>

将字符串中的第一个字符转换为大写等效字符。

版本:TypeScript 2.0+

type LowercaseGreeting = "hello, world";  
type Greeting = Capitalize<LowercaseGreeting>; // type Greeting = "Hello, world"  

Uncapitalize<StringType>

将字符串中的第一个字符转换为小写等效字符。

版本:TypeScript 3.5+

type UppercaseGreeting = "HELLO WORLD";  
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>; // type UncomfortableGreeting = "hELLO WORLD"