你应该掌握的 5 大 TypeScript 功能

web前端开发

共 5000字,需浏览 10分钟

 ·

2021-08-15 21:59

英文 | https://betterprogramming.pub/top-5-typescript-features-you-should-master-2358db9ab3d5

翻译 | 杨小二


TypeScript 的影响力与日俱增。它现在是任何新的 Web/Node 项目的首选配套工具。使用 TypeScript 的好处怎么强调都不为过。然而,了解和理解这个 JavaScript 超集拥有的所有工具是很重要的。

你是否正在投入时间来提高你的TypeScript技能?你想充分利用它吗?有时,由于没有使用正确的 TypeScript 功能并且没有遵循其最佳实践,可能会出现大量代码重复和样板。

在本文中,我们将研究 TypeScript 可以赋予我们的五个最重要的功能。通过确保并了解它们的用例,我们可以构建更好、更全面的代码库。

1、Unions

联合是最基本且易于使用的 TypeScript 功能之一。它们让我们可以轻松地将多种类型合二为一。交集和联合类型是我们组合类型的方法之一。

function logIdentifier(id: string | number) {  console.log('id', id);}

当我们想要表示某个类型可以为空时,它们非常有用:

function logIdentifier(id: string | undefined) {  if(!id) {    console.error('no identifier found');  } else {    console.log('id', id);  }}

不仅限于未定义或原语。它们可用于任何接口或类型。

interface Vehicle {  speed: number;}interface Bike extends Vehicle {  ride: () => void;}interface Plane extends Vehicle {  fly: () => void;}function useVehicle(vehicle: Bike | Plane) {  ...}

鉴于上面的联合类型,我们如何区分自行车和飞机?通过使用可区分联合功能。我们将创建一个名为 Vehicles 的枚举并将其用作属性值。

看看代码如何:

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 可重用的最佳方法是什么?泛型! 这是大多数类型语言中的一项功能。它让我们以更通用的方式表达类型。这将赋予我们的类和类型。

让我们从一个基本的例子开始。让我们创建一个方法来将任何定义的类型添加到数组中:

function addItem(item: string, array: string[]) {  array = [...array, item];  return array;}

如果我们想为 int 类型创建相同的实用程序怎么办?我们应该重做同样的方法吗?通过简单地使用泛型,我们可以重用代码而不是添加更多样板:

function addItem<T>(item: T, array: T[]) {  array = [...array, item];  return array;}
addItem('hello', []);
addItem(true, [true, true]);

我们如何防止在 T 中使用不需要的类型?为此,我们可以使用 extends 关键字:

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 的文档

最重要的一点是这些数组的值长度是固定的。定义元组有两种方式:

明确:

const array: [string, number] = ['test', 12];

隐含地:

const array = ['test', 12] as const;

唯一的区别是 as const 将使数组只读,这在我看来是可取的。

请注意,元组也可以被标记:

function foo(x: [startIndex: number, endIndex: number]) {  ...}

标签不需要我们在解构时以不同的方式命名我们的变量。它们纯粹是为了文档和工具。标签将有助于使我们的代码更具可读性和可维护性。

请注意,使用标记元组时有一个重要规则:标记元组元素时,元组中的所有其他元素也必须被标记。

4、映射类型

什么是映射类型?它们是一种避免反复定义接口的方法。你可以将类型建立在另一种类型或接口的基础上,从而节省手动工作。

“当你不想重复时,有时一种类型需要基于另一种类型。映射类型建立在索引签名的语法之上,用于声明尚未提前声明的属性类型。” — TypeScript 的文档

总而言之,映射类型允许我们基于现有类型创建新类型。

TypeScript 确实附带了很多实用程序类型,因此我们不必在每个项目中重写它们。

让我们看看一些最常见的:Omit、Partial、Readonly、Readonly、Exclude、Extract、NonNullable 和 ReturnType。

让我们看看其中的一个再行动。假设我们要将名为 Teacher 的实体的所有属性转换为只读。我们可以使用什么实用程序?

我们可以使用 Readonly 实用程序类型。让我们看看它的实际效果:

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 在底层是如何工作的:

type Readonly<T> = { readonly [P in keyof T]: T[P]; }

现在让我们创建我们的自定义实用程序以获得乐趣。让我们反转 Readonly 类型以创建一个 Writable 类型:

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 运算符转换为类型保护函数:

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 可能是字符串或数字。

让我们看另一个使用自定义接口的类型保护示例:

interface Hunter {    hunt: () => void;}
// function type guardfunction 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 的能力。现在由你来进一步深入研究其中任何一个。
通过尝试逐步采用它们,你将看到你的代码如何变得更整洁、更干净、更易于维护。
感谢你的阅读,祝编程愉快!

学习更多技能

请点击下方公众号


浏览 26
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报