这 5 个 TypeScript 的功能特征,你需要熟悉下

2021-09-08 16:28:17 浏览数 (1)

英文 | 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 的能力。现在由你来进一步深入研究其中任何一个。

通过尝试逐步采用它们,你将看到你的代码如何变得更整洁、更干净、更易于维护。

感谢你的阅读,祝编程愉快!

0 人点赞