TypeScript进阶(三)类型演算与高级内置类型

2023-11-28 13:58:50 浏览数 (1)

引言

--

TypeScript 是一种静态类型检查的 JavaScript 超集,它通过类型注解和类型推断来提供更强大的类型系统。在 TypeScript 中,类型演算是一种重要的概念,它允许我们在编译时对类型进行操作和计算。本文将深入探讨 TypeScript 类型演算的原理和应用。

基本概念


在 TypeScript 中,类型是一种值的属性。通过将值与其对应的类型进行关联,我们可以在编译时检查代码中的类型错误。而类型演算则是对这些类型进行操作和计算的过程。

TypeScript 提供了一系列内置的操作符和关键字来进行类型演算。例如,typeof 操作符可以用于获取一个值的类型;keyof 关键字可以用于获取一个对象所有属性名组成的联合类型;in 关键字可以用于遍历一个联合类型中所有成员等等。

类型推断与上下文


在 TypeScript 中,编译器会根据上下文自动推断变量或表达式的类型。这种基于上下文的推断机制使得代码更加简洁且易读。

例如,在以下代码中:

代码语言:typescript复制
let x = 10;

编译器会自动推断变量 x 的类型为 number。这是因为赋给 x 的值是一个数字字面量。

类型操作符


TypeScript 提供了一系列类型操作符,用于对类型进行操作和计算。这些操作符包括联合类型(|)、交叉类型(&)、索引访问操作符([])、条件类型(extends ? :)等等。

例如,我们可以使用联合类型来定义一个变量可以接受多种不同类型的值:

代码语言:typescript复制
let x: number | string;

这样,变量 x 可以接受 number 类型或 string 类型的值。

条件类型


条件类型是 TypeScript 中一种非常强大的类型演算工具。它允许我们根据某个条件来选择不同的类型。

例如,我们可以使用条件类型来实现一个根据输入参数的不同返回不同结果的函数:

代码语言:typescript复制
type Result<T> = T extends number ? string : boolean;

function getResult<T>(input: T): Result<T> {
  if (typeof input === "number") {
    return "number";
  } else {
    return true;
  }
}

在上述代码中,如果输入参数是一个数字,则返回字符串类型;否则返回布尔值。

映射类型


映射类型是 TypeScript 中一种非常有用的工具,它允许我们根据已有的对象定义新的对象类型。

例如,我们可以使用映射类型来将一个对象中所有属性都设置为只读:

代码语言:typescript复制
type ReadonlyObject<T> = {
  readonly [P in keyof T]: T[P];
};

const obj: ReadonlyObject<{ name: string; age: number }> = {
  name: "Alice",
  age: 20,
};

obj.name = "Bob"; // Error: Cannot assign to 'name' because it is a read-only property.

类型守卫


类型守卫是 TypeScript 中一种用于缩小类型范围的机制。通过使用类型守卫,我们可以在特定条件下判断一个值的类型,并在代码块中使用该类型。

例如,我们可以使用 typeof 操作符来判断一个值的类型,并在代码块中使用该类型:

代码语言:typescript复制
function printValue(value: string | number) {
  if (typeof value === "string") {
    console.log(value.toUpperCase());
  } else {
    console.log(value.toFixed(2));
  }
}

在上述代码中,如果 value 是一个字符串,则将其转换为大写字母并打印;否则将其保留两位小数并打印。

类型演算的应用


通过深入理解 TypeScript 类型演算,我们可以更好地利用 TypeScript 的强大类型系统来编写更安全、更健壮的代码。

例如,在开发过程中,我们经常需要对输入参数进行验证和处理。通过使用条件类型和映射类型等工具,我们可以根据输入参数的不同来选择不同的处理逻辑,并根据已有对象定义新的对象类型。

此外,在编写通用库或框架时,类型演算也是非常有用的。通过使用类型演算,我们可以实现更加灵活和可扩展的类型定义,从而提供更好的类型推断和代码提示。

操作符


当谈到 TypeScript 类型演算时,typeof、keyof 和 in 是三个非常重要的操作符和关键字。它们在类型系统中扮演着不同的角色,用于获取类型信息、操作对象属性和遍历联合类型成员。下面将详细讲解它们的作用及使用示例。

typeof 操作符

typeof 是一个在 JavaScript 中已经存在的操作符,用于获取一个值的类型。在 TypeScript 中,typeof 操作符也可以用于获取一个值的类型,并将其作为一个类型注解或类型声明使用。

代码语言:typescript复制
let x = 10;
let y: typeof x; // y 的类型为 number

在上述代码中,typeof x 返回 number 类型,并将其赋值给变量 y。

keyof 关键字

keyof 是 TypeScript 中的一个关键字,用于获取一个对象所有属性名组成的联合类型。通过 keyof 关键字,我们可以在编译时获取对象属性名,并将其作为一个类型注解或类型声明使用。

代码语言:typescript复制
type Person = {
 name: string;
 age: number;
};

type PersonKeys = keyof Person; // PersonKeys 的类型为 "name" | "age"

在上述代码中,keyof Person 返回 "name" | "age" 类型,并将其赋值给 PersonKeys。

in 关键字

in 是 TypeScript 中的一个关键字,用于遍历一个联合类型中所有成员。通过 in 关键字,我们可以在编译时对联合类型进行遍历,并将其作为一个类型注解或类型声明使用。

代码语言:typescript复制
type Fruit = "apple" | "banana" | "orange";

type FruitInfo = {
 [P in Fruit]: number;
};

const fruitCount: FruitInfo = {
 apple: 5,
 banana: 3,
 orange: 2,
};

在上述代码中,[P in Fruit] 表示遍历 Fruit 联合类型中的所有成员,并将其作为 FruitInfo 对象的属性名,属性值的类型为 number。

通过使用 typeof、keyof 和 in,我们可以在 TypeScript 中对类型进行操作和计算,从而实现更加灵活和可扩展的类型定义。这些操作符和关键字在实际开发中非常有用,可以帮助我们编写更安全、更健壮的代码,并提高开发效率。

内置类型


  • Omit<T, K>
  • Pick<T, K>
  • Record<K, T>
  • Partial<T>
  • Required<T>
  • Readonly<T>
  • Exclude<T, U>
  • Extract<T, U>
  • NonNullable<T> 
  • ReturnType<T>
  • InstanceType<T>
Omit<T, K>

Omit<T, K> 用于从类型 T 中排除指定属性 K。它会创建一个新的类型,其中排除了属性 K。

代码语言:typescript复制
type Person = {
 name: string;
 age: number;
 address: string;
};
type PersonWithoutAddress = Omit<Person, 'address'>;
// PersonWithoutAddress 的类型为 { name: string; age: number; }

在上述代码中,Omit<Person, 'address'> 从 Person 类型中排除了属性 'address'。

Pick<T, K>

用于从类型 T 中选择指定属性 K。它会创建一个新的类型,其中只包含属性 K。

代码语言:typescript复制
type Person = {
 name: string;
 age: number;
 address: string;
};
type PersonNameAndAge = Pick<Person, 'name' | 'age'>;
// PersonNameAndAge 的类型为 { name: string; age: number; }

在上述代码中,Pick<Person, 'name' | 'age'> 从 Person 类型中选择了属性 'name' 和 'age'。

Record<K, T>

用于创建一个新的对象类型,其中键为类型 K 中的值,值为类型 T。

代码语言:typescript复制
type Fruit = 'apple' | 'banana' | 'orange';
type FruitCount = Record<Fruit, number>;
// FruitCount 的类型为 { apple: number; banana: number; orange: number; }

在上述代码中,Record<Fruit, number> 创建了一个对象类型,其中键为 Fruit 类型中的值,值为 number 类型。

Partial<T>

用于将类型 T 中的所有属性变为可选属性。它会创建一个新的类型,其中所有属性都变为可选。

代码语言:typescript复制
type Person = {
  name: string;
  age: number;
};

type PartialPerson = Partial<Person>;
// PartialPerson 的类型为 { name?: string; age?: number; }

在上述代码中,Partial<Person> 将 Person 类型中的所有属性变为可选属性。

Required<T>

用于将类型 T 中的所有属性变为必选属性。它会创建一个新的类型,其中所有属性都变为必选。

代码语言:typescript复制
type PartialPerson = {
  name?: string;
  age?: number;
};

type RequiredPerson = Required<PartialPerson>;
// RequiredPerson 的类型为 { name: string; age: number; }

在上述代码中,Required<PartialPerson> 将 PartialPerson 类型中的所有属性变为必选属性。

Readonly<T>

用于将类型 T 中的所有属性变为只读属性。它会创建一个新的类型,其中所有属性都变为只读。

代码语言:typescript复制
type Person = {
  name: string;
  age: number;
};

type ReadonlyPerson = Readonly<Person>;
// ReadonlyPerson 的类型为 { readonly name: string; readonly age: number; }

在上述代码中,Readonly<Person> 将 Person 类型中的所有属性变为只读属性。

Exclude<T, U>

用于从类型 T 中排除类型 U。它会创建一个新的类型,其中排除了类型 U 的成员。

代码语言:typescript复制
type Fruit = "apple" | "banana" | "orange";
type ExcludeFruit = Exclude<Fruit, "banana">;
// ExcludeFruit 的类型为 "apple" | "orange"

在上述代码中,Exclude<Fruit, "banana"> 从 Fruit 类型中排除了值为 "banana" 的成员。

Extract<T, U>

用于从类型 T 中提取出类型 U。它会创建一个新的类型,其中只包含类型 U 的成员。

代码语言:typescript复制
type Fruit = "apple" | "banana" | "orange";
type ExtractFruit = Extract<Fruit, "banana">;
// ExtractFruit 的类型为 "banana"

在上述代码中,Extract<Fruit, "banana"> 从 Fruit 类型中提取出值为 "banana" 的成员。

NonNullable<T> 

用于从类型 T 中排除 null 和 undefined。它会创建一个新的类型,其中不包含 null 和 undefined。

代码语言:typescript复制
type Value = string | null | undefined;
type NonNullableValue = NonNullable<Value>;
// NonNullableValue 的类型为 string

在上述代码中,NonNullable<Value> 从 Value 类型中排除了 null 和 undefined。

ReturnType<T>

用于获取函数类型 T 的返回值类型。它会创建一个新的类型,其中只包含函数 T 的返回值类型。

代码语言:typescript复制
type MyFunction = () => string;
type MyFunctionReturnType = ReturnType<MyFunction>;
// MyFunctionReturnType 的类型为 string

在上述代码中,ReturnType<MyFunction> 获取了函数类型 MyFunction 的返回值类型。

InstanceType<T>

用于获取构造函数类型 T 的实例类型。它会创建一个新的类型,其中只包含构造函数 T 的实例类型。

代码语言:typescript复制
class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

type PersonInstance = InstanceType<typeof Person>;
// PersonInstance 的类型为 Person

在上述代码中,InstanceType<typeof Person> 获取了构造函数 Person 的实例类型。

Awaited<T> 

用于获取 Promise 类型 T 的解析值类型。它会创建一个新的类型,其中包含了 Promise 类型 T 的解析值类型。

代码语言:typescript复制
async function fetchData(): Promise<string> {
  return "Data";
}

type AwaitedData = Awaited<ReturnType<typeof fetchData>>;
// AwaitedData 的类型为 string

在上述代码中,Awaited<ReturnType<typeof fetchData>> 获取了 fetchData 函数返回 Promise 的解析值类型。

Parameters<T> 

用于获取函数类型 T 的参数类型组成的元组。它会创建一个新的类型,其中包含了函数 T 的参数类型组成的元组。

代码语言:typescript复制
function greet(name: string, age: number): void {
  console.log(`Hello, ${name}! You are ${age} years old.`);
}

type GreetParams = Parameters<typeof greet>;
// GreetParams 的类型为 [string, number]

在上述代码中,Parameters<typeof greet> 获取了 greet 函数的参数类型组成的元组。

通过这些 TypeScript 类型操作符,我们可以更灵活地操作和计算类型,并提供更强大的静态类型检查。这些操作符在实际开发中非常有用,可以帮助我们编写更安全、更健壮的代码,并提高开发效率。

总结

--

本文深入探讨了 TypeScript 类型演算的原理和应用。通过使用类型演算,我们可以在编译时对类型进行操作和计算,从而提供更强大的类型系统。通过合理地运用类型推断、条件类型、映射类型等工具,我们可以编写更安全、更健壮的代码,并提高开发效率。希望本文能够帮助读者深入理解 TypeScript 类型演算,并在实际开发中得到应用。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

0 人点赞