英文 | https://betterprogramming.pub/top-5-typescript-features-you-should-master-2358db9ab3d5
翻译 | 杨小二
TypeScript 的影响力与日俱增。它现在是任何新的 Web/Node 项目的首选配套工具。使用 TypeScript 的好处怎么强调都不为过。然而,了解和理解这个 JavaScript 超集拥有的所有工具是很重要的。
你是否正在投入时间来提高你的TypeScript技能?你想充分利用它吗?有时,由于没有使用正确的 TypeScript 功能并且没有遵循其最佳实践,可能会出现大量代码重复和样板。
在本文中,我们将研究 TypeScript 可以赋予我们的五个最重要的功能。通过确保并了解它们的用例,我们可以构建更好、更全面的代码库。
1、Unions
联合是最基本且易于使用的 TypeScript 功能之一。它们让我们可以轻松地将多种类型合二为一。交集和联合类型是我们组合类型的方法之一。
代码语言:javascript复制function logIdentifier(id: string | number) {
console.log('id', id);
}
当我们想要表示某个类型可以为空时,它们非常有用:
代码语言:javascript复制function logIdentifier(id: string | undefined) {
if(!id) {
console.error('no identifier found');
} else {
console.log('id', id);
}
}
不仅限于未定义或原语。它们可用于任何接口或类型。
代码语言:javascript复制interface Vehicle {
speed: number;
}
interface Bike extends Vehicle {
ride: () => void;
}
interface Plane extends Vehicle {
fly: () => void;
}
function useVehicle(vehicle: Bike | Plane) {
...
}
鉴于上面的联合类型,我们如何区分自行车和飞机?通过使用可区分联合功能。我们将创建一个名为 Vehicles 的枚举并将其用作属性值。
看看代码如何:
代码语言:javascript复制enum Vehicles {
bike,
plane
}
interface Vehicle {
speed: number;
type: Vehicles;
}
interface Bike extends Vehicle {
ride: () => void;
type: Vehicles.bike;
}
interface Plane extends Vehicle {
fly: () => void;
type: Vehicles.plane;
}
function useVehicle(vehicle: Bike | Plane) {
if (vehicle.type === Vehicles.bike) {
vehicle.ride();
}
if (vehicle.type === Vehicles.plane) {
vehicle.fly();
}
}
从而,我们可以看到Unions是一个简单而强大的工具,它有一些技巧。但是,如果我们想以更强大和动态的方式表达类型/接口,我们需要使用泛型。
2、泛型
使我们的方法/API 可重用的最佳方法是什么?泛型! 这是大多数类型语言中的一项功能。它让我们以更通用的方式表达类型。这将赋予我们的类和类型。
让我们从一个基本的例子开始。让我们创建一个方法来将任何定义的类型添加到数组中:
代码语言:javascript复制function addItem(item: string, array: string[]) {
array = [...array, item];
return array;
}
如果我们想为 int 类型创建相同的实用程序怎么办?我们应该重做同样的方法吗?通过简单地使用泛型,我们可以重用代码而不是添加更多样板:
代码语言:javascript复制function addItem<T>(item: T, array: T[]) {
array = [...array, item];
return array;
}
addItem('hello', []);
addItem(true, [true, true]);
我们如何防止在 T 中使用不需要的类型?为此,我们可以使用 extends 关键字:
代码语言:javascript复制function addItem<T extends boolean | string>(item: T, array: T[]) {
array = [...array, item];
return array;
}
addItem('hello', []);
addItem(true, [true, true]);
addItem(new Date(), []);
// ^^^^^^^^^^
// Argument of type 'Date' is not assignable to parameter of type 'string | boolean'
泛型将使我们能够为我们的类型构建全面和动态的接口。它们是必须掌握的功能,需要在我们的日常开发中出现。
3、元组
什么是元组?我们来看看定义:
“元组类型允许你用固定数量的元素来表达数组,这些元素的类型是已知的,但不必相同。例如,你可能希望将一个值表示为一对字符串和一个数字。”
——TypeScript 的文档
最重要的一点是这些数组的值长度是固定的。定义元组有两种方式:
明确:
代码语言:javascript复制const array: [string, number] = ['test', 12];
隐含的:
代码语言:javascript复制const array = ['test', 12] as const;
唯一的区别是 as const 将使数组只读,这在我看来是可取的。
请注意,元组也可以被标记:
代码语言:javascript复制function foo(x: [startIndex: number, endIndex: number]) {
...
}
标签不需要我们在解构时以不同的方式命名我们的变量。它们纯粹是为了文档和工具。标签将有助于使我们的代码更具可读性和可维护性。
请注意,使用标记元组时有一个重要规则:标记元组元素时,元组中的所有其他元素也必须被标记。
4、映射类型
什么是映射类型?它们是一种避免反复定义接口的方法。你可以将类型建立在另一种类型或接口的基础上,从而节省手动工作。
“当你不想重复时,有时一种类型需要基于另一种类型。映射类型建立在索引签名的语法之上,用于声明尚未提前声明的属性类型。” — TypeScript 的文档
总而言之,映射类型允许我们基于现有类型创建新类型。
TypeScript 确实附带了很多实用程序类型,因此我们不必在每个项目中重写它们。
让我们看看一些最常见的:Omit、Partial、Readonly、Readonly、Exclude、Extract、NonNullable 和 ReturnType。
让我们看看其中的一个再行动。假设我们要将名为 Teacher 的实体的所有属性转换为只读。我们可以使用什么实用程序?
我们可以使用 Readonly 实用程序类型。让我们看看它的实际效果:
代码语言:javascript复制interface Teacher {
name: string;
email: string;
}
type ReadonlyTeacher = Readonly<Teacher>;
const t: ReadonlyTeacher = { name: 'jose', email: 'jose@test.com'};
t.name = 'max'; // Error: Cannot assign to 'name' because it is a read-only property.(2540)
让我们回顾一下Readonly 在底层是如何工作的:
代码语言:javascript复制type Readonly<T> = { readonly [P in keyof T]: T[P]; }
现在让我们创建我们的自定义实用程序以获得乐趣。让我们反转 Readonly 类型以创建一个 Writable 类型:
代码语言:javascript复制interface Teacher {
readonly name: string;
readonly email: string;
}
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
const t: Writeable<Teacher> = { name: 'jose', email: 'jose@test.com' };
t.name = 'max'; // works fine
注意:注意 - 修饰符。在这种情况下,它用于删除 readonly 修饰符。它可用于从属性中删除其他修饰符,例如 ?。
5、类型保护
类型保护是一组帮助我们缩小对象类型的工具。这意味着我们可以从更一般的类型转到更具体的类型。
有多种技术可以执行类型保护。在本文中,我们将只关注用户定义的类型保护。这些基本上是断言——就像任何给定类型的函数一样。
我们如何使用它们?我们只需要定义一个函数,它的返回类型是一个类型谓词,它返回true/false。让我们看看如何将 typeof 运算符转换为类型保护函数:
代码语言:javascript复制function isNumber(x: any): x is number {
return typeof x === "number";
}
function add1(value: string | number) {
if (isNumber(value)) {
return value 1;
}
return value 1;
}
请注意,如果 isNumber 检查为 false,则 TypeScript 可以假定 value 将是一个字符串,因为 x 可能是字符串或数字。
让我们看另一个使用自定义接口的类型保护示例:
代码语言:javascript复制interface Hunter {
hunt: () => void;
}
// function type guard
function isHunter(x: unknown): x is Hunter {
return (x as Hunter).hunt !== undefined;
}
const performAction = (x: unknown) => {
if (isHunter(x)) {
x.hunt();
}
}
const animal = {
hunt: () => console.log('hunt')
}
performAction(animal);
注意 isHunter 函数的返回类型是 x is Hunter。该断言函数将成为我们的类型保护。
类型保护是有作用域的。在 isHunter(x) 代码块中,x 变量的类型为 Hunter。这意味着我们可以安全地调用它的hunt 方法。然而,在这个代码块之外,x 类型仍然是未知的。
最后的想法
在本文中,我们只是探讨了我们可以使用的最重要的 Typescript 功能。由于这只是一个概述,我们只是触及了它们的表面。
我的目标是让你好奇并展示 Typescript 的能力。现在由你来进一步深入研究其中任何一个。
通过尝试逐步采用它们,你将看到你的代码如何变得更整洁、更干净、更易于维护。
感谢你的阅读,祝编程愉快!