返回

用 TypeScript 扩展 type 类型(下)

前端

TypeScript 类型系统详解

在 JavaScript 的基础上,TypeScript 引入了一套强大的类型系统。它不仅可以帮助开发者在编译阶段就发现潜在的错误,还能提升代码的可读性和可维护性。本文将深入浅出地探讨 TypeScript 类型系统的核心概念,并通过丰富的示例帮助你理解和应用它们。

函数类型:定义函数的输入和输出

函数是程序的基本组成部分,而函数类型则了函数的输入参数类型和返回值类型。TypeScript 使用简洁的语法来定义函数类型:

(参数1类型, 参数2类型, ...) => 返回值类型

举个例子,假设我们需要一个函数,它接受两个数字作为参数,并返回它们的和。我们可以用以下方式定义它的类型:

(num1: number, num2: number) => number

我们可以将这个类型赋给一个变量,然后将一个符合该类型的函数赋值给它:

const add: (num1: number, num2: number) => number = (a, b) => a + b;

let sum = add(5, 3); // sum 的类型会被推断为 number

索引类型:对象的结构

索引类型用于描述对象的属性类型。它使用方括号 [] 来表示索引,并用冒号 : 来分隔索引类型和属性类型。

例如,假设我们需要一个对象,它的属性名是字符串,属性值是数字。我们可以用以下方式定义它的类型:

{ [key: string]: number }

我们可以将这个类型赋给一个变量,然后创建一个符合该类型的对象:

const scores: { [key: string]: number } = {
  Alice: 95,
  Bob: 88,
  Charlie: 92,
};

字符串映射类型:基于现有类型创建新类型

字符串映射类型允许我们基于现有的类型创建新的类型,它会遍历现有类型的属性,并根据指定的规则生成新的属性。

例如,假设我们有一个类型 Person

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

我们可以使用字符串映射类型创建一个新的类型 PartialPerson,它包含 Person 的所有属性,但这些属性都是可选的:

type PartialPerson = { [K in keyof Person]?: Person[K] };

在这个例子中,keyof Person 会获取 Person 类型的所有属性名(nameage),然后 [K in keyof Person] 会遍历这些属性名,并为每个属性添加一个 ?,表示该属性是可选的。

字面量类型:精确描述特定值

字面量类型是指只能取特定值的类型。例如,我们可以定义一个只能取值为 'red''green''blue' 的类型:

type Color = 'red' | 'green' | 'blue';

let myColor: Color = 'red'; // 正确
myColor = 'yellow'; // 错误,'yellow' 不是 Color 类型

枚举类型:定义一组命名常量

枚举类型用于定义一组命名常量。它使用 enum 来定义,每个成员都有一个名称和一个值。

例如,我们可以定义一个表示星期的枚举类型:

enum Weekday {
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
  Sunday,
}

let today: Weekday = Weekday.Monday;

联合类型和交叉类型:组合不同类型

联合类型表示一个值可以是多种类型之一。它使用 | 符号来连接不同的类型。

例如,我们可以定义一个可以是数字或字符串的类型:

type NumberOrString = number | string;

let value: NumberOrString = 10;
value = 'hello';

交叉类型表示一个值必须同时满足多个类型。它使用 & 符号来连接不同的类型。

例如,我们可以定义一个既是数字又是字符串的类型(虽然这种情况很少见):

type NumberAndString = number & string;

类型保护:缩小类型的范围

类型保护是一种机制,它允许我们在特定代码块中缩小一个值的类型范围。

例如,我们可以使用 typeof 操作符来检查一个值的类型:

function logValue(value: number | string) {
  if (typeof value === 'number') {
    console.log(value.toFixed(2)); // 在这个代码块中,value 的类型是 number
  } else {
    console.log(value.toUpperCase()); // 在这个代码块中,value 的类型是 string
  }
}

条件类型:根据条件选择类型

条件类型允许我们根据一个类型的类型来创建新的类型。它使用 extends 关键字来判断一个类型是否可以赋值给另一个类型。

例如,我们可以定义一个条件类型,如果 T 类型可以赋值给 string 类型,则返回 string 类型,否则返回 number 类型:

type TypeIsString<T> = T extends string ? string : number;

type StringType = TypeIsString<string>; // string
type NumberType = TypeIsString<number>; // number

类型推断:自动推断类型

类型推断是指编译器根据上下文自动推断一个值的类型。

例如,在以下代码中,编译器会自动推断 age 的类型为 number

let age = 10;

类型兼容性:判断类型是否可以相互赋值

类型兼容性是指判断两个类型是否可以相互赋值。TypeScript 使用结构类型系统来判断类型兼容性,也就是说,如果两个类型的结构相同,则它们是兼容的。

例如,以下两个类型是兼容的:

interface Point {
  x: number;
  y: number;
}

interface Point3D {
  x: number;
  y: number;
  z: number;
}

let point: Point = { x: 1, y: 2 };
let point3d: Point3D = point; // 正确,Point 可以赋值给 Point3D

常见问题解答

1. 什么是 TypeScript 中的泛型?

泛型允许我们编写可以处理多种类型的代码,而无需为每种类型都编写特定的代码。例如,我们可以编写一个泛型函数,它可以处理任何类型的数组:

function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

2. 如何定义可选属性?

在属性名后面添加一个 ? 符号,表示该属性是可选的:

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

3. 如何定义只读属性?

在属性名前面添加一个 readonly 关键字,表示该属性是只读的:

interface Person {
  readonly name: string;
  age: number;
}

4. 什么是 TypeScript 中的命名空间?

命名空间用于组织代码,避免命名冲突。它使用 namespace 关键字来定义:

namespace MyNamespace {
  export function foo() {
    // ...
  }
}

5. 如何使用 TypeScript 声明文件?

声明文件用于为 JavaScript 库提供类型信息。它使用 .d.ts 作为文件扩展名。例如,我们可以为 jQuery 库编写一个声明文件:

// jquery.d.ts
declare var $: (selector: string) => any;