跳到内容

b

TypeScript的一小步

在简单介绍了TypeScript的主要原理后,我们现在准备开始成为FullStack TypeScript开发者的旅程。

我们不会给你一个关于TypeScript所有方面的彻底介绍,而是在这一部分重点介绍用TypeScript开发Express后端或React前端时最常见的问题。

除了语言特性,我们还将着重强调工具的使用。

Setting things up

在你选择的编辑器中安装TypeScript支持。Visual Studio Code可以与TypeScript原生工作。

如前所述,TypeScript代码本身是不可执行的。它必须首先被编译成可执行的JavaScript。

当TypeScript被编译成JavaScript时,代码就会成为类型清除的对象。这意味着类型注释、接口、类型别名和其他类型系统结构被移除,结果是纯粹的可运行的JavaScript。

在生产环境中,编译的需要往往意味着你必须设置一个 "构建步骤"。在构建步骤中,所有的TypeScript代码都被编译成一个单独的文件夹中的JavaScript,然后生产环境从该文件夹中运行代码。在开发环境中,为了能更快地看到所产生的变化,利用实时编译和自动重载往往更方便。

让我们开始编写我们的第一个TypeScript应用。为了保持简单,让我们从使用npm包ts-node开始。它可以立即编译并执行指定的TypeScript文件,因此不需要单独的编译步骤。

你可以通过运行ts-node和官方的typescript包全局安装。

npm install -g ts-node typescript

如果你不能或不想安装全局包,你可以创建一个具有所需依赖性的npm项目并在其中运行你的脚本。

我们也将采取这种方法。

正如我们在第三章节中所记得的,一个npm项目是通过在一个空目录中运行命令npm init来设置的。然后,我们可以通过运行

npm install --save-dev ts-node typescript

并在package.json中设置scripts

{
  // ..
  "scripts": {
    "ts-node": "ts-node"  },
  // ..
}

你现在可以通过运行npm run ts-node在这个目录中使用ts-node。注意,如果你通过package.json使用ts-node,脚本的所有命令行参数需要以--为前缀。因此,如果你想用ts-node运行file.ts,整个命令是。

npm run ts-node -- file.ts

值得一提的是,TypeScript还提供了一个在线游乐场,在那里你可以快速尝试TypeScript代码,并立即看到产生的JavaScript和可能的编译错误。你可以访问TypeScript's official playground here

NB: 游戏场可能包含与你的本地环境不同的tsconfig规则(将在后面介绍),这就是为什么你可能在那里看到与你的本地环境不同的警告。Playground's tsconfig可以通过config下拉菜单进行修改。

A note about the coding style

JavaScript本身是一种相当宽松的语言,事情往往可以用多种不同的方式完成。例如,我们有命名与匿名函数,使用const和let或var,以及使用分笔记。这部分课程与其他课程的不同之处在于使用分号。这不是TypeScript特有的模式,而是在创建任何类型的JavaScript项目时采取的一般编码风格决定。是否使用它们通常由程序员自己决定,但由于它被期望使一个人的编码习惯适应现有的代码库,你被期望使用分号并适应这部分练习的编码风格。与课程的其他部分相比,这一部分还有一些其他的编码风格差异,例如,在目录命名惯例方面。

让我们为项目添加一个配置文件tsconfig.json,内容如下。

{
  "compilerOptions":{
    "noImplicitAny": false
  }
}

tsconfig.json文件用于定义TypeScript编译器应该如何解释代码,编译器应该如何严格工作,哪些文件需要观察或忽略,以及很多很多

现在我们将只使用编译器选项noImplicitAny,它不要求对所有使用的变量都有类型。

让我们从创建一个简单的乘法器开始。它看起来和JavaScript中的一模一样。

const multiplicator = (a, b, printText) => {
  console.log(printText,  a * b);
}

multiplicator(2, 4, 'Multiplied numbers 2 and 4, the result is:');

正如你所看到的,这仍然是普通的基本JavaScript,没有额外的TS功能。它用npm run ts-node -- multiplier.ts很好地编译和运行,就像用Node一样。

但是如果我们最终向乘法器函数传递了错误的类型参数,会发生什么?

我们来试试吧!

const multiplicator = (a, b, printText) => {
  console.log(printText,  a * b);
}

multiplicator('how about a string?', 4, 'Multiplied a string and 4, the result is:');

现在当我们运行这段代码时,输出是。将一个字符串和4相乘,结果是。NaN

如果语言本身能够防止我们陷入这样的情况,那不是很好吗?

这就是我们看到TypeScript的第一个好处的地方。 让我们为参数添加类型,看看它能给我们带来什么。

TypeScript原生支持多种类型,包括numberstringArray。请看全面的列表这里。更复杂的自定义类型也可以被创建。

我们函数的前两个参数是数字类型,最后一个是字符串

const multiplicator = (a: number, b: number, printText: string) => {
  console.log(printText,  a * b);
}

multiplicator('how about a string?', 4, 'Multiplied a string and 4, the result is:');

现在的代码不再是有效的JavaScript,而是事实上的TypeScript。当我们试图运行这段代码时,我们注意到它并没有被编译。

fullstack content

TypeScript编辑器支持的最好的一点是,你甚至不一定需要运行代码就能看到问题。

VSCode插件是如此高效,以至于当你试图使用一个不正确的类型时,它会立即通知你。

fullstack content

Creating your first own types

让我们把我们的乘法器扩展成一个稍微通用的计算器,同时支持加法和除法。这个计算器应该接受三个参数:两个数字和操作,要么是multiply, add或者divide,告诉它如何处理这些数字。

在JavaScript中,代码将需要额外的验证,以确保最后一个参数确实是一个字符串。TypeScript提供了一种为输入定义特定类型的方法,它确切地描述了什么类型的输入是可接受的。在此基础上,TypeScript还可以在编辑器级别显示已接受的值的信息。

我们可以使用TypeScript本地关键字type创建一个type。让我们来描述我们的类型Operation

type Operation = 'multiply' | 'add' | 'divide';

现在,Operation类型只接受三种输入;正是我们想要的三个字符串。

使用OR运算符|,我们可以通过创建一个联合类型来定义一个变量,接受多个值。

在这个例子中,我们使用了精确的字符串(在技术术语中,被称为字符串字面类型),但是通过联合,你也可以让编译器同时接受字符串和数字。string | number

type关键字为一个类型定义了一个新的名字。一个类型的别名。由于定义的类型是三个可能的值的联合体,给它一个具有代表性的名字的别名是很方便的。

我们现在来看看我们的计算器。

type Operation = 'multiply' | 'add' | 'divide';

const calculator = (a: number, b: number, op : Operation) => {
  if (op === 'multiply') {
    return a * b;
  } else if (op === 'add') {
    return a + b;
  } else if (op === 'divide') {
    if (b === 0) return 'can't divide by 0!';
    return a / b;
  }
}

现在,当我们把鼠标悬停在计算器函数中的操作类型上面时,我们可以立即看到关于如何处理它的建议。

fullstack content

如果我们试图使用一个不在操作类型内的值,我们会得到熟悉的红色警告信号和来自编辑器的额外信息。

fullstack content

这已经很不错了,但我们还没有触及的一件事是输入函数的返回值。通常情况下,你想知道一个函数的返回值,如果能保证它确实返回了它所说的东西,那就更好了。让我们在计算器函数中添加一个返回值number

type Operation = 'multiply' | 'add' | 'divide';

const calculator = (a: number, b: number, op: Operation): number => {
  if (op === 'multiply') {
    return a * b;
  } else if (op === 'add') {
    return a + b;
  } else if (op === 'divide') {
    if (b === 0) return 'this cannot be done';
    return a / b;
  }
}

编译器会直接产生警告,因为在一种情况下,该函数会返回一个字符串。有几种方法可以解决这个问题。我们可以扩展返回类型以允许字符串值,像这样。

const calculator = (a: number, b: number, op: Operation): number | string =>  {
  // ...
}

或者我们可以创建一个返回类型,其中包括两种可能的类型,就像我们的操作类型。

type Result = string | number;

const calculator = (a: number, b: number, op: Operation): Result =>  {
  // ...
}

但现在的问题是,这个函数是否真的可以返回一个字符串?

当你的代码最终会出现某样东西被0整除的情况时,可能是出了很大的问题,应该在调用函数的地方抛出一个错误并加以处理。

当你决定返回你原本不期望的值时,你从TypeScript中看到的警告会阻止你做出仓促的决定,并帮助你保持你的代码按预期运行。

还有一件事要考虑,即使我们已经为我们的参数定义了类型,在运行时使用的生成的JavaScript并不包含类型检查。

因此,比如说,如果operation参数的值来自于一个外部接口,就不能明确保证它是允许的值之一。因此,最好还是包括错误处理,并准备好应对意外情况的发生。

在这种情况下,当有多个可能接受的值,并且所有意外的值都应该导致一个错误,switch...case语句比我们代码中的if...else更适合。

我们计算器的代码实际上应该是这样的。

type Operation = 'multiply' | 'add' | 'divide';

type Result = number;
const calculator = (a: number, b: number, op: Operation) : Result => {  switch(op) {
    case 'multiply':
      return a * b;
    case 'divide':
      if (b === 0) throw new Error('Can't divide by 0!');      return a / b;
    case 'add':
      return a + b;
    default:
      throw new Error('Operation is not multiply, add or divide!');  }
}

try {
  console.log(calculator(1, 5 , 'divide'));
} catch (error: unknown) {
  let errorMessage = 'Something went wrong.'
  if (error instanceof Error) {
    errorMessage += ' Error: ' + error.message;
  }
  console.log(errorMessage);
}

从TypeScript 4.0开始,catch块允许你指定捕获子句变量的类型。在4.4之前,所有的catch子句变量都是any类型。然而,随着4.4版本的发布,默认类型为unknown未知是一种顶层类型,在TypeScript第3版中被引入,成为any的类型安全对应物。任何东西都可以赋值给unknown,但是unknown除了它自己和any之外,如果没有类型断言或基于控制流的缩小,则不能赋值给任何东西。同样地,如果不首先断言或缩小到一个更具体的类型,就不允许对unknown进行操作。

我们写的程序还不错,但如果我们能使用命令行参数,而不是总是要改变代码来计算不同的东西,那肯定会更好。

让我们试试吧,就像在普通的Node应用中那样,通过访问process.argv。如果你使用的是最新的npm版本(7.0或更高版本),就不会有问题,但如果是旧的设置,就不太对劲了。

fullstack content

那么,在旧的设置中,问题出在哪里?

@types/{npm_package}

让我们回到TypeScript的基本理念。TypeScript希望所有全局使用的代码都是类型化的,当你的项目有一个合理的配置时,它对你自己的代码也是如此。TypeScript库本身只包含TypeScript包的代码类型。你可以为一个库编写自己的类型,但这几乎是不需要的--因为TypeScript社区已经为我们做了这个工作

与npm一样,TypeScript世界也在庆祝开源代码。社区很活跃,不断对常用的npm包的更新和变化做出反应。你几乎总能找到npm包的类型,所以你不必单独为你的成千上万的依赖创建类型。

通常,现有软件包的类型可以从npm内部的@types组织中找到,你可以通过安装一个带有@types/前缀的软件包名称的npm包将相关类型添加到你的项目中。比如说npm install --save-dev @types/react @types/express @types/lodash @types/jest @types/mongoose等等,等等。@types/*Definitely typed维护,这是一个社区项目,目的是在一个地方维护所有的类型。

有时,一个npm包也可以在代码中包含它的类型,在这种情况下,安装相应的@types/*就没有必要。

NB: 由于类型只在编译前使用,所以在生产构建中不需要类型,它们应该总是在package.json的devDependencies中。

由于全局变量process是由Node本身定义的,我们从包@types/node中获得其类型。

从10.0版本开始,ts-node已经将@types/node定义为一个对等依赖。如果npm的版本至少是7.0,那么一个项目的对等依赖就会自动被npm安装。如果你有一个更老的npm,同行依赖必须明确安装。

npm install --save-dev @types/node

当包@types/node被安装时,编译器不会产生警告变量process。请注意,不需要在代码中要求类型,安装包就足够了!

Improving the project

接下来,让我们添加npm脚本来运行我们的两个程序multipliercalculator

{
  "name": "fs-open",
  "version": "1.0.0",
  "description": "",
  "main": "index.ts",
  "scripts": {
    "ts-node": "ts-node",
    "multiply": "ts-node multiplier.ts",    "calculate": "ts-node calculator.ts"  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "ts-node": "^10.5.0",
    "typescript": "^4.5.5"
  }
}

我们可以通过以下改动让乘法器使用命令行参数工作。

const multiplicator = (a: number, b: number, printText: string) => {
  console.log(printText,  a * b);
}

const a: number = Number(process.argv[2])
const b: number = Number(process.argv[3])
multiplicator(a, b, `Multiplied ${a} and ${b}, the result is:`);

而且我们可以用以下方式运行它。

npm run multiply 5 2

如果程序运行时的参数类型不对,比如说

npm run multiply 5 lol

它 "工作 "了,但给我们的答案是。

Multiplied 5 and NaN, the result is: NaN

其原因是,Number("lol")返回NaN

这实际上是number类型,所以TypeScript没有能力将我们从这种情况下拯救出来。

为了防止这种行为,我们必须验证从命令行给我们的数据。

乘法器的改进版如下所示:

interface MultiplyValues {
  value1: number;
  value2: number;
}

const parseArguments = (args: Array<string>): MultiplyValues => {
  if (args.length < 4) throw new Error('Not enough arguments');
  if (args.length > 4) throw new Error('Too many arguments');

  if (!isNaN(Number(args[2])) && !isNaN(Number(args[3]))) {
    return {
      value1: Number(args[2]),
      value2: Number(args[3])
    }
  } else {
    throw new Error('Provided values were not numbers!');
  }
}

const multiplicator = (a: number, b: number, printText: string) => {
  console.log(printText,  a * b);
}

try {
  const { value1, value2 } = parseArguments(process.argv);
  multiplicator(value1, value2, `Multiplied ${value1} and ${value2}, the result is:`);
} catch (error: unknown) {
  let errorMessage = 'Something bad happened.'
  if (error instanceof Error) {
    errorMessage += ' Error: ' + error.message;
  }
  console.log(errorMessage);
}

当我们现在运行这个程序时。

npm run multiply 1 lol

我们会得到一个合适的错误信息。

Something bad happened. Error: Provided values were not numbers!

函数parseArguments的定义有几个有趣的地方。

const parseArguments = (args: Array<string>): MultiplyValues => {
  // ...
}

首先,参数args是一个字符串的数组。返回值的类型是MultiplyValues,其定义如下。

interface MultiplyValues {
  value1: number;
  value2: number;
}

这个定义利用了TypeScript的Interface关键字,这是定义一个对象应该具有的 "形状 "的一种方式。

在我们的例子中,很明显,返回值应该是一个具有两个属性value1value2的对象,它们都应该是数字类型。

More about tsconfig

到目前为止,我们只使用了一条tsconfig规则noImplicitAny。这是一个很好的开始,但现在是时候深入研究一下配置文件了。

如前所述,tsconfig.json文件包含了你希望TypeScript在你的项目中如何工作的所有核心配置。

让我们在我们的tsconfig.json文件中指定以下配置。

{
  "compilerOptions": {
    "target": "ES2020",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "esModuleInterop": true,
    "moduleResolution": "node"
  }
}

不要太担心compilerOptions;它们将在以后被仔细检查。

你可以从TypeScript文档中找到每个配置的解释,或者从非常方便的tsconfig页面,或者从tsconfig模式定义,不幸的是它的格式比前两个选项要差一点。

Adding Express to the mix

现在,我们处于一个相当好的位置。我们的项目已经设置好了,而且我们有两个可执行的计算器在里面。

然而,由于我们的目标是学习FullStack开发,现在是时候开始处理一些HTTP请求了。

让我们从安装Express开始。

npm install express

然后在package.json中添加start脚本。

{
  // ..
  "scripts": {
    "ts-node": "ts-node",
    "multiply": "ts-node multiplier.ts",
    "calculate": "ts-node calculator.ts",
    "start": "ts-node index.ts"  },
  // ..
}

现在我们可以创建文件index.ts,并将HTTP GET ping端点写入其中。

const express = require('express');
const app = express();

app.get('/ping', (req, res) => {
  res.send('pong');
});

const PORT = 3003;

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

其他一切似乎都工作得很好,但是,正如你所期望的,app.getreqres参数需要输入。如果你仔细观察,VSCode也在产生警告关于导入Express的一些问题。你可以看到在require下面有一条黄色的短线点。让我们把目光停留在这个问题上。

fullstack content

投诉的内容是:"require"的调用可能被转换为导入。让我们听从建议,把导入写成下面的样子。

import express from 'express';

nb:VSCode为你提供了一种可能性,通过点击Quick Fix...按钮自动修复这些问题。请留意这些助手/快速修复;听从你的编辑器通常会使你的代码更好,更容易阅读。对问题的自动修复也可以是一个主要的时间节省者。

现在我们遇到了另一个问题,编译器产生警告导入语句的问题。

再一次,当试图找出问题所在时,编辑器是我们最好的朋友。

fullstack content

我们还没有为express安装类型。

让我们按建议做并运行。

npm install --save-dev @types/express

再也没有错误了!让我们来看看有什么变化。

当我们把目光停留在require语句上时,我们可以看到编译器把所有与表达有关的东西都解释为any类型。

fullstack content

而当我们使用import时,编辑器知道实际类型。

fullstack content

使用哪种导入语句取决于导入包中使用的导出方法。

一个好的经验法则是先尝试用import语句导入一个模块。我们总是在前端中使用这种方法。

如果 import 不起作用,请尝试使用组合方法。import ... = require(''...')

我们强烈建议你阅读更多关于TypeScript模块的信息这里

这段代码还有一个问题。

fullstack content

这是因为我们在tsconfig.json中禁止了未使用的参数。

{
  "compilerOptions": {
    "target": "ES2020",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "esModuleInterop": true
  }
}

如果你有库中的预定义函数,需要声明一个变量,即使它根本没有被使用,这种配置可能会产生问题,这里就是这样。

幸运的是,这个问题已经在配置层面得到了解决。

再一次将鼠标悬停在这个问题上,我们得到了一个解决方案。这一次我们可以直接点击快速修复按钮。

fullstack content

如果绝对不可能摆脱一个未使用的变量,你可以在它前面加上一个下划线,通知编译器你已经考虑过了,你也无能为力。

我们把req变量重命名为_req。最后我们准备启动应用。它似乎工作得很好。

fullstack content

为了简化开发,我们应该启用自动重载来改善我们的工作流程。在本课程中,你已经使用了nodemon,但是ts-node有一个替代品,叫做ts-node-dev。它只适用于开发环境,在每次修改时都会进行重新编译,所以重新启动应用是不必要的。

让我们把ts-note-dev安装到我们的开发依赖项中。

npm install --save-dev ts-node-dev

package.json中添加一个脚本。

{
  // ...
  "scripts": {
      // ...
      "dev": "ts-node-dev index.ts",  },
  // ...
}

现在,通过运行npm run dev,我们的项目有了一个可以工作的、自动重载的开发环境!

The horrors of any

现在我们已经完成了第一个端点,你可能注意到我们在这些小例子中几乎没有使用任何TypeScript。当仔细检查代码时,我们可以看到有一些危险潜伏在那里。

让我们把HTTP POST端点calculate添加到我们的应用。

import { calculator } from './calculator';

// ...

app.post('/calculate', (req, res) => {
  const { value1, value2, op } = req.body;

  const result = calculator(value1, value2, op);
  res.send(result);
});

当你把鼠标悬停在calculate函数上时,你可以看到calculator的打字,尽管代码本身并不包含任何打字。

fullstack content

但是如果你把鼠标悬停在从请求中解析出来的值上,就会出现一个问题。

fullstack content

所有的变量都有any类型。这并不令人惊讶,因为还没有人给他们一个类型。有几种方法可以解决这个问题,但首先,我们必须考虑为什么会接受这个问题,以及类型any是怎么来的。

在TypeScript中,每一个没有类型的变量,其类型不能被隐式推断出来,就变成了类型any。Any是一种 "通配符 "类型,字面意思是代表whatever类型。

当人们忘记对函数进行类型化时,事情就会经常变成隐含的任何类型。

我们也可以显式地给事物输入any。隐式和显式任意类型之间的唯一区别是代码的外观;编译器并不关心这种区别。

然而,当any被显式执行时,程序员看到的代码与隐式推断时不同。

隐式的any类型通常被认为是有问题的,因为它经常是由于程序员忘记了分配类型(或者懒得分配),而且它也意味着TypeScript的全部功能没有被正确利用。

这就是为什么配置规则noImplicitAny存在于编译器级别,并且强烈建议在任何时候都保持它。

在少数情况下,当你真的不知道一个变量的类型是什么时,你应该在代码中明确说明。

const a : any = /* no clue what the type will be! */.

我们已经在我们的例子中配置了noImplicitAny,那么为什么编译器没有产生警告隐含的any类型?

原因是express Request对象的query字段是明确的类型any。我们用来向应用发布数据的request.body字段也是如此。

如果我们想阻止开发者使用any类型呢?幸运的是,除了tsconfig.json,我们还有其他方法来强制执行编码风格。我们可以做的是使用eslint来管理

我们的代码。

让我们安装eslint和它的TypeScript扩展。

npm install --save-dev eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser

我们将把eslint配置为disallow explicit any。在.eslintrc中写入以下规则。

{
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 11,
    "sourceType": "module"
  },
  "plugins": ["@typescript-eslint"],
  "rules": {
    "@typescript-eslint/no-explicit-any": 2  }
}

(新版本的eslint默认有这个规则,所以你不一定需要单独添加它。)

让我们也设置一个lint npm脚本,通过修改package.json文件来检查扩展名为.ts的文件。

{
  // ...
  "scripts": {
      "start": "ts-node index.ts",
      "dev": "ts-node-dev index.ts",
      "lint": "eslint --ext .ts ."      //  ...
  },
  // ...
}

现在如果我们试图定义一个any类型的变量,lint会产生警告。

fullstack content

@typescript-eslint有很多TypeScript专用的eslint规则,但你也可以在TypeScript项目中使用所有基本的eslint规则。

现在,我们也许应该使用推荐的设置,当我们发现我们想改变行为的时候,我们会在进行中修改规则。

在推荐设置的基础上,我们应该尽量熟悉这部分所要求的编码风格,将每行代码末尾的分号设置为必填项

所以我们将使用下面的.eslintrc

{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking"
  ],
  "plugins": ["@typescript-eslint"],
  "env": {
    "node": true,
    "es6": true
  },
  "rules": {
    "@typescript-eslint/semi": ["error"],
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off",
    "@typescript-eslint/restrict-template-expressions": "off",
    "@typescript-eslint/restrict-plus-operands": "off",
    "@typescript-eslint/no-unused-vars": [
      "error",
      { "argsIgnorePattern": "^_" }
    ],
    "no-case-declarations": "off"
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "project": "./tsconfig.json"
  }
}

少了不少分号,但这些很容易添加。我们还必须解决ESlint中关于any类型的问题。

fullstack content

我们可以而且应该禁用一些ESlint规则来从请求体中获取数据。

禁用@typescript-eslint/no-unsafe-assignment的destructuring赋值就差不多了。

app.post('/calculate', (req, res) => {
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment  const { value1, value2, op } = req.body;

  const result = calculator(Number(value1), Number(value2), op);
  res.send(result);
});

然而这仍然有一个问题需要处理,函数调用中的最后一个参数是不安全的。

fullstack content

我们可以禁用另一条ESlint规则来解决这个问题。

app.post('/calculate', (req, res) => {
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
  const { value1, value2, op } = req.body;

  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument  const result = calculator(Number(value1), Number(value2), op);
  res.send(result);
});

我们现在得到了ESlint的沉默,但我们完全受用户的摆布。我们肯定应该对帖子的数据进行一些验证,如果数据无效,就给出一个适当的错误信息。

app.post('/calculate', (req, res) => {
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
  const { value1, value2, op } = req.body;

  if ( !value1 || isNaN(Number(value1))) {    return res.send({ error: '...'}).status(400);  }
  // more validations here...

  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
  const result = calculator(Number(value1), Number(value2), op);
  return res.send(result);
});