likes
comments
collection
share

TypeScript 顶部类型与底部类型,借你看看我的小笔记~

作者站长头像
站长
· 阅读数 74

TypeScript 是一种由 Microsoft 开发的 JavaScript 的超集,它在 JavaScript 的基础上添加了类型和其他特性。通过使用 TypeScript,开发者可以在编写代码时获得更好的类型检查和代码提示,从而提高代码的可读性、健壮性和可维护性。

一、TypeScript 中的类型系统

在 TypeScript 中,类型是一个重要概念。类型指定变量或函数的数据类型,从而使编译器能够对代码进行更严格的类型检查。TypeScript 提供了多种类型,包括基本类型(如 number、string、boolean 等)、对象类型、数组类型、函数类型等。通过使用这些类型,开发者可以在编写代码时获得更好的类型检查和代码提示。

let count: number = 5;
let name: string = 'John';
let isStudent: boolean = true;

function add(x: number, y: number): number {
  return x + y;
}

let person: { name: string, age: number } = { name: 'John', age: 30 };
let numbers: number[] = [1, 2, 3, 4, 5];

在以上示例中,定义了一些具体的类型,并将其应用到变量、函数、对象、数组等不同的场景中。由于指定了具体的类型,TypeScript 在编译时会对代码进行类型检查,如果发现类型不匹配的情况,就会产生编译错误,从而帮助开发者尽早地发现和排除问题。

除了基本类型之外,TypeScript 还支持更复杂的类型,例如联合类型、交叉类型、可选属性、只读属性等。这些类型可以帮助开发者更准确地描述变量和函数的类型,从而获得更好的类型检查和代码提示。例如:

type Gender = 'male' | 'female';
type Address = { street: string, city: string, zipCode: string };

interface Person {
  name: string;
  age: number;
  gender?: Gender;
  address?: Address;
}

let person: Person = { name: 'John', age: 30, gender: 'male' };

在以上示例中,使用 type 关键字定义了 Gender 和 Address 类型,它们分别表示性别和地址信息。使用 interface 关键字定义了一个 Person 接口,它包含了 name、age、gender 和 address 四个属性,其中 gender 和 address 属性是可选的。接着,定义了一个变量 person,并将其声明为 Person 类型的对象。在编写代码时,开发者可以根据具体情况指定 person 对象的属性,从而使编译器能够对代码进行更严格的类型检查。

TypeScript 中的类型系统是一项非常强大的功能,它可以帮助开发者在编写代码时获得更好的类型检查和代码提示。通过合理地使用这些特性,开发者可以提高代码的可读性、健壮性和可维护性,从而更加高效地完成工作。

二、TypeScript 中的顶部类型和底部类型

在 TypeScript 中,有两种特殊的类型:顶部类型和底部类型。顶部类型是可以被赋值为任何类型的类型,而底部类型则是所有类型的子类型。

2.1 any 类型

any 类型是一种顶部类型,它包含所有可能的值,并且可以转换为任何其他类型。使用 any 类型可以放弃对变量的类型检查。在某些情况下,any 类型是有用的,例如当开发者需要存储来自不同来源的数据时,或者当在处理未知类型的值时。

any 类型可以被赋值为任何类型,它相当于放弃 TypeScript 对该变量的类型检查。any 类型的变量可以调用任何方法,访问任何属性,并且可以被赋值为任何值。使用 any 类型的主要目的是为了兼容 JavaScript 中的动态类型特性或者在 TypeScript 中无法确定变量类型的情况。

在使用 any 类型时需要注意潜在的风险。因为 TypeScript 将不会检查 any 类型的变量,所以如果在使用 any 变量时出现错误,TypeScript 编译器将不会提示。

下面是一个使用 any 类型的示例:

let variable: any = 'Hello World';
variable = 123;
variable = { name: 'John', age: 30 };

在上述示例中,变量 variable 被定义为 any 类型,并且可以被赋值为 string、number 和 object 类型的值,因为任何类型都可以转换为 any 类型。

但是,使用 any 类型也有一些缺点。由于在编译时不对变量进行类型检查,因此在运行时可能会出现类型错误。因此,建议只在必要时使用 any 类型,并尽可能使用更具体的类型。

2.2 unknown 类型

unknown 类型与 any 类型类似,也是一种顶部类型。它包含所有可能的值,但在使用时需要进行类型检查。与 any 类型不同的是,unknown 类型可以帮助开发者消除代码中的类型不确定性。

unknown 类型与 any 类型类似,也可以被赋值为任何类型。不同的是,在使用 unknown 类型时,编译器会对变量进行类型检查。这意味着在使用 unknown 类型时,我们必须使用类型断言或者类型防护来确定变量的实际类型。

unknown 类型通常用于参数类型不确定的情况。例如,在处理用户输入时,我们无法确定输入的类型,因此可以使用 unknown 类型来存储输入变量。然后使用类型断言或类型防护来检查变量的实际类型。

下面是一个使用 unknown 类型的示例:

function processValue(value: unknown) {
  if (typeof value === 'string') {
    console.log(value.toUpperCase());
  }
}

在上述示例中,函数 processValue 接收一个参数 value,它的类型是 unknown。在函数体内,使用 typeof 运算符检查 value 的类型是否为 string。如果是,则将 value 转换为大写字母输出。

由于使用了 unknown 类型,编译器可以确保在使用 value 变量时先进行类型检查,从而避免在运行时出现类型错误。因此,unknown 类型是一种更安全的顶部类型,可以帮助开发者消除代码中的类型不确定性。

2.3 never 类型

never 类型是 TypeScript 中的底部类型。它表示函数不会返回任何值,或者代码执行会导致永远无法正常结束。例如,在执行 throw 语句或进入无限循环时,函数的返回类型为 never。

never 类型表示不可能发生的值的类型。never 类型通常用于以下情况:

  1. 抛出异常或中止程序;
  2. 无限循环或递归;
  3. 类型错误或断言失败。

下面是一个使用 never 类型的示例:

a) 抛出异常

function throwError(message: string): never {
  throw new Error(message);
}

在上述示例中,函数 throwError 抛出一个包含指定消息的异常。由于函数永远不会返回值,因此它的返回类型为 never。

b) 无限循环

function infiniteLoop(): never {
  while (true) {}
}

在上述示例中,函数 infiniteLoop 启动一个无限循环。由于函数永远不会返回值,因此它的返回类型为 never。

c) 类型错误

function processValue(value: string | number): never {
  throw new TypeError(`Expected string or number, but got ${typeof value}.`);
}

在上述示例中,函数 throwError 抛出一个包含指定消息的异常。由于函数永远不会返回值,因此它的返回类型为 never。

三、类型断言与类型保护

TypeScript 提供了类型断言和类型保护机制,可以将已知类型的变量转换为更具体的类型或排除不可能的类型,以提高代码的类型安全性。

3.1 类型断言

类型断言是一种强制类型转换的方法,在 TypeScript 中可以使用 as 关键字或尖括号语法进行。

下面是一个使用 as 关键字的示例:

const inputValue: unknown = 'Hello World';
const outputValue = (inputValue as string).toUpperCase();
console.log(outputValue); // 'HELLO WORLD'

在上述示例中,变量 inputValue 的类型是 unknown,但是因为我们确切知道值是一个字符串,所以使用 as 关键字将其强制转换为 string 类型,并将输出值转换为大写字母。

3.2 类型保护

类型保护是一种机制,可以在编译时排除不可能的类型,从而减少代码中类型检查的需要。TypeScript 提供了多种类型保护机制,包括 typeof、instanceof 和 in 操作符等。

3.2.1 typeof 类型保护

使用 typeof 操作符可以在运行时检查变量的类型。typeof 操作符可以用于以下类型:string、number、bigint、boolean、symbol 和 function。

下面是一个使用 typeof 类型保护的示例:

function processValue(input: string | number) {
  if (typeof input === 'string') {
    console.log(input.toUpperCase());
  } else {
    console.log(input.toFixed(2));
  }
}

在上述示例中,函数 processValue 接收一个参数 input,它可以是 string 或 number 类型。在函数体中,使用 typeof 操作符检查 input 的类型。如果是 string 类型,则将其转换为大写字母输出;否则将其转换为保留两位小数的数字输出。

3.2.2 instanceof 类型保护

使用 instanceof 操作符可以在运行时检查变量是否属于指定类型的实例。

下面是一个使用 instanceof 类型保护的示例:

class Animal {}
class Dog extends Animal {}

function processAnimal(animal: Animal) {
  if (animal instanceof Dog) {
    console.log('It is a dog');
  } else {
    console.log('It is an animal');
  }
}

在上述示例中,类 Animal 是基类,类 Dog 是 Animal 的子类。函数 processAnimal 接收一个类型为 Animal 的参数,并使用 instanceof 进行类型保护。如果参数是 Dog 类型的实例,则输出字符串 'It is a dog',否则输出字符串 'It is an animal'。

3.2.3 in 操作符类型保护

使用 in 操作符可以在运行时检查对象是否包含指定属性。

下面是一个使用 in 操作符类型保护的示例:

interface Square {
  width: number
  height: number;
}

function processShape(shape: Square | Circle) { 
    if ('radius' in shape) {
        console.log('It is a circle');
    } else { 
        console.log('It is a square'); 
    } 
}

在上述示例中,接口 Square 定义了一个正方形的宽度和高度属性。函数 processShape 接收一个参数 shape,可以是 Square 或 Circle 类型。使用 in 操作符进行类型保护,检查 shape 是否包含 radius 属性,如果包含,则说明参数为 Circle 类型的对象,否则为 Square 类型的对象。

四、总结

本文介绍了 TypeScript 中的顶部类型和底部类型。顶部类型包括 any 类型和 unknown 类型,底部类型是 never 类型。本文还介绍了 TypeScript 中的类型断言和类型保护机制,可以在编写代码时提高类型安全性。开发者可以根据实际需求,在不同场景下使用这些特性,以提高代码的可读性、健壮性和可维护性。

转载自:https://juejin.cn/post/7238570834343329851
评论
请登录