如何将你的项目迁移成 TypeScript

我们都曾遇到过,一个 JavaScript 开发的项目,发现了一个 bug, 调试了半天,发现问题原因是 undefined is not a function。我个人很喜欢 JavaScript,但随着项目功能的增多,代码会越来越多,某些问题会一次又一次地出现。 为了避免 undefined is not a function 类似 bug,我们可以为类型加定义。TypeScript就是来帮助我们解决这类问题的工具。

JavaScript 中引入了一个类型系统,使您能够更好地理解代码,并帮助您完成开发流程。但我们必须面对一个现实,很多人选择静态类型语言,就是因为不用为所有变量都定义类型。

先别急着关网页,耐心继续往下看。你可以使用工具使你的项目迁移到 TypeScript。像 Webpack 就已经成功的使用了 TypeScript。我们在后面的文章中讨论这个问题。

这篇文章将会教你如何将 TypeScript 加入到自己的项目中。你也可以边看,边在你的项目中实践。

  • 你可能已经在使用 TypeScript
  • VS Code 实现 JavaScript 编程
  • 使用 TypeScript 编译 Javascript
    • 安装 TypeScript 编译器
    • 创建一个 TypeScript 配置
    • Javascript 配置 TypeScript 编译器
    • 使用 TypeScript 检查你的 Javascript
    • 寻找隐藏的 bugs
  • Javascript 文件转换成 TypeScript 文件
  • 严格模式:引入更多的类型
  • 使用 TypeScript 维护库
  • TypeScript 为您效劳

请注意:文章不会介绍 TypeScript 这门语言。而是介绍你如何按自己的需要,将 TypeScript 作为工具使用。

你可能已经在使用 TypeScript

即使你以前没有写过一行 TypeScript, 但你也有可能已经使用过 TypeScript 了。

首先,要明确一个概念,TypeScript 包含 JavaScript,意味着任意有效的 Javascript 都可以转换成有效的 TypeScript。如果你成功的执行了 JavaScript,就意味着你已经成功的写了 TypeScript。所有没有明确类型的定义, TypeScript 都会试图尽可能的猜测类型。

由于每个有效的 Javascript 都是有效的 TypeScript,所以ni 很有可能使用了 TypeScript 的工具。 Monaco 编辑器—— 在 Visual Studio CodeCodeSandboxStackblitz 和其他编辑器后的编辑器——由 TypeScript提供更好的自动补全功能或者类似代码重构的功能。 所以,如果您曾经使用过这些编辑器的自动补全或重构功能,那么您使用的是 TypeScript

VS Code 实现 JavaScript 编程

我们大部分时候不会使用 TypeScript 编译器和 TypeScript 文件。更多的是用普通的 JavaScript 工具。所以我们需要编译器来帮助我们。

Vs Code里有一个设置,您可以为整个编辑器启用该设置,也可以为特定的工作区或项目启用该设置,以便在 JavaScript 上启用 TypeScript 检查。

若要为项目启用它,请在根项目目录中创建一个文件 .vscode/settings.json。然后把下面的东西放进去:

{
 "javascript.implicitProjectConfig.checkJs": true
}

或者,您可以将相同的规则放入全局编辑器的设置中,或者使用设置 UI 编辑工作区设置。

现在,如果你写了一行非安全代码测试一下,TypeScript 会在你的编辑器中为你标注出来。例如,这段代码会产生一个提示:

let PORT = process.env.PORT || 3000;
PORT += 1;

process.env.PORT 是字符串类型,但是 3000 是数字类型。此时 PORT += 1 就会有不同的两种类型的结果。字符串的话,就会追加一个字符 1 在尾部,但是数字的话就会是 3001 的结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pc8nQZgk-1579141267620)(https://twilio-cms-prod.s3.amazonaws.com/images/Yy6PGxzdZAgeCOa31cDBrrs41XEU8qT4-BwPfhxC8VLOZ.width-1000.png)]

这个错误提示并不会影响代码,因为它们只在编辑器中显示。即使抛出了错误,但你的代码仍然会运行。与此同时,提醒您有可能在测试过程中没有考虑到情况。

但如果你有意想违反某些规则,那这种设置有时也会令人讨厌。在这种情况下,那你可以打开它,或者通过在每行上面添加下面的注释来禁用类型检查。

// @ts-ignore

要禁用全部类型检查,可以使用以下命令:

// @ts-nocheck

我个人很喜欢这个设置,并且通常都会开启检查。因为它会让我思考我编写的每一行 JavaScript 代码,是否可能出现我从未考虑到的边缘情况。这对我们写代码来说,有很大帮助。

使用 TypeScript 编译 Javascript

我们在 VS Code 中启用的设置实际上是在 TypeScript 编译器上设置了一个叫做 checkJs 的设置。它与另一个名为 allowJs 的设置有关,该设置通过 TypeScript 解析器运行 JavaScript 文件。这是将现有的 JavaScript 项目逐步转换为 TypeScript 的完美开始。

为了向你展示它是如何工作的,我将使用下面的项目作为示例: github.com/dkundel/ts-move-demo

大家可以 clone 到本地按步骤学习。

安装 TypeScript 编译器

首先我们要安装 TypeScript 编译器。TypeScript 可以从 npm 上下载,然后对外暴露了 tsc 的命令。 可以在不同的项目中安装不同版本的编辑器,也可以在全局安装。

要在本地为项目运行安装 TypeScript 编译器:

npm install -D typescript

我还建议在 package.jsonscripts 部分设置一个新的构建脚本:

"scripts": {
   "start": "node .",
   "build": "tsc",
   "test": "echo \"Error: no test specified\" && exit 1"
 },

构建可以使用 npm run build,如果想运行 TypeScript ,也可以在观察模式, npm run build -- --watch,或者执行另一个脚本, tsc --watch

创建一个 TypeScript 配置

如果你不想经常通过命令行参数向 TypeScript 编译器传递一大堆配置选项。那我建议你创建一个 tsconfig.json。有两种方法可以做到这一点。可以手动创建文件,也可以执行 tsc --init 来创建默认配置。在我们的例子中,因为我们有一个调用 tsc 的构建脚本,所以我们可以运行:

npm run build -- --init

然后你会在你的项目中找到一个 tsconfig.json,它包含了所有可用的设置,并有注释说明它们是做什么的。 其中一些被注释掉了,而另一些则被设置为 TypeScript 认为的最佳实践。

这是个推荐设置,但对于我们项目来说可能不是最佳的。

注意:如果 tsc 无法运行,可以尝试。

sudo npm install -g concurrently
sudo npm install -g lite-server
sudo npm install -g typescript
tsc --init

Javascript 配置 TypeScript 编译器

第一步,我们希望确保 TypeScript 编译器能够接收到我们的 JavaScript 文件。

用以下代码替换现有的 tsconfig.json:

{
 "compilerOptions": {
   "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
   "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
   "allowJs": true /* Allow javascript files to be compiled. */,
   "checkJs": false /* Report errors in .js files. */,
   "outDir": "./dist" /* Redirect output structure to the directory. */,
   "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
   "strict": false /* Enable all strict type-checking options. */,
   "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
   "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
 }
}

在这个配置中,我们修改了5个默认设置:

  1. "target": "esnext": 我们把这个改成 esnext。这意味着 TypeScript 不会执行任何现代 Javascript 特性。这样你就可以继续使用像 Babel 这样的工具来编译。但你可以改变这个值来测试一下 es5。 看看代码有什么变化。
  1. "allowJs": true:TypeScript 会找到任意 JavaScript 文件,并通过 TypeScript 编译器运行它。
  1. "checkJs": false:禁用了 JavaScript 文件的类型检查,因为我们只想看看它们是否正确地通过编译器传递。
  1. “outDir”: “./dist”:编译器将每个文件都处理后放入一个新的 dist/ 目录中的位置。
  1. “rootDir”: “./src”:在某些情况下,你可以省略这个设置,特别是在 JSTS 文件都存在的过渡时期。我建议你把代码放到一个目录中,如 src , lib 或者其他你喜欢的文件名下,作为代码的根目录,这样更便于编译器找到所有的代码,进行处理和输出结果。
  1. “strict”: false:为了能够优雅地将我们的项目移动到 TypeScript 中,暂时禁用严格模式。

示例项目已经将所有文件移动到一个 src/ 文件夹中,但如果您正在尝试使用不同的项目或使用不同的文件夹,请确保要么移动文件,要么调整 rootDir 值。

此外,您还必须调整引用源文件的路径以映射到编译好的文件中。在我们的示例中,这意味着更新 package.json 文件:

- "main": "src/index.js",
+ "main": "dist/index.js",
 "scripts": {

设置好了之后,运行一下:

npm run build

如果您更改了 target值,这可能会创建一个新的 dist 文件夹,其中包含所有的 JavaScript 文件。

然后,您可以运行代码,看看是否能够正常运行。在我们的例子中:

npm start

访问 http://localhost:3000,看看服务器是否运行正常。

使用 TypeScript 检查你的 JavaScript

现在我们所有的 JavaScript 都通过了 TypeScript 编译器,那接下来我们可以对 JavaScript 启用类型检查了。 更新 tsconfig.json 中的设置:

{
 "compilerOptions": {
   "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
   "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
   "allowJs": true /* Allow javascript files to be compiled. */,
   "checkJs": true /* Report errors in .js files. */,
   "outDir": "./dist" /* Redirect output structure to the directory. */,
   "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
   "strict": false, /* Enable all strict type-checking options. */
   "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,

   "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
 }
}

如果您正在使用 Node.jsElectron 或其他具有全局对象的东西,而这些对象不是 DOM 的一部分,那么您必须在这一步中为它安装类型。 对于 Node.js,你可以通过运行以下命令:

npm install -D @types/node

安装了类型之后,就可以编译代码了。我们可能会遇到一些类型错误,所以我建议在观察模式下运行 TypeScript 编译器,这样当你修复文件的时候,它就可以不断地重新运行:

npm run build -- --watch

会尽最大努力在你的 JavaScript 代码中做类型假设,但是如果存在 JSDoc 注释,它也会考虑这些。

如果您在编写项目时已经在 VS 代码 中启用了 checkJs,那么您可能几乎没有什么需要修正的地方。 这个开关,可能遇到的大多数错误都与缺少校验有关,比如在访问 document.getElementById() 之前检查结果是否为 null
其他错误可能是基于您所做的类型假设而产生的实际错误。

无论哪种情况,你都可能会发现一些你以前可能没有想到的事情。如果你是故意做某些事情的,那你可以使用 // @ts-ignore 注释来忽略它。

寻找隐藏的 bug

一旦安装了编译器之后,修复了提示的错误,你可能就会避免很多 bug。 事实上,这就是 Webpack 也正在用。 Webpack 也是用 JavaScript编写的,用 TypeScript 来检查错误。当 Webpack 在2018年4月引入 TypeScript 时,尽管代码已经经过了大量的测试,他们依然发现了各种各样的错误,这多亏了编译器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vRM6AX2R-1579141267621)(https://twilio-cms-prod.s3.amazonaws.com/images/fYmcOP_V0S3O_VDCQSNuevRulPKsVkv48jiS1lgpwPiC6.width-1000.png)]

如果你想像他们一样使用 TypeScript,甚至不需要修改你的代码,你可以以下面的方式更新你的配置:

{
 "compilerOptions": {
   "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
   "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
   "allowJs": true /* Allow javascript files to be compiled. */,
   "checkJs": false /* Report errors in .js files. */,
-   "outDir": "./dist" /* Redirect output structure to the directory. */,
   "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
+   "noEmit": true /* Do not emit outputs. */,
   "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
   "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
 }
}

这样,编译器将更像一个附加的链接程序,而不是编译器。

JavaScript 文件转换为 TypeScript 文件

在确保我们的文件在 JavaScript 中是尽可能类型安全的之后,下一步是将它们慢慢逐个地转换为 TypeScript 文件。 因为我们启用了 allowJs,所以你不必一次性全部移动,可以慢慢迁移。

因为我们已经在 JavaScript 代码上运行了类型检查,所以我们不需要更新太多。

对于您来说,第一步是将文件扩展名为从 js 重命名为 ts。告诉编译器这不再是 JavaScript,而是 TypeScript,并支持定义类型等其他语法特性。

最可能需要修改的是 importexport 的方式。 如果你已经在使用 ES 模块 语法,那没问题! 但如果您正在使用 CommonJS 语法(基本上使用 require()module.exports) ,那么您必须进行调整,因为 TypeScript 使用 ES Modules 语法。

将每个 require() 调用替换为 import 语法中的等效调用,即每次 exports.* 转向 export,而不用 module.exports

看下我们的示例, src/routes/data.js 文件。

- const { Router } = require('express');
+ import { Router } from 'express';
 
- const routes = Router();
+ export const routes = Router();
 
routes.get('/', (req, res) => {
 res.send({
   hello: 'world',
 });
});
 
- module.exports = routes;

module.exportsES 模块export default 是不等价的。export default x 相当于 export default = x。但是因为我们在 tsconfig.json 里设置了 esModuleInterop,如果我们使用 module.exportsimport 一个 js 文件,TypeScript 将会提醒我们,
。例如:

import express from 'express'

假如我们需要把 Javascript 写成 TypeScript。那就意味着我们需要升级我们的引用方式:

- const dataRoutes = require('./routes/data');
+ const { routes: dataRoutes } = require('./routes/data');
// or in ES Modules / TypeScript
+ import { routes as dataRoutes } from './routes/data';

作为一个通用原则,我们应该尽量避免使用 module.exports = x 来默认导出。对于 ES 模块来说,module.exports = { default: x } 这样更好。

导入导出修改好之后,运行下项目,看是否一切正常。同理,把每个文件都迁移到 TypeScript 上。

严格模式:引入更多的类型

目前为止,虽然看起来我们已经迁移到了 TypeScript ,但其实我们还没有用到 JSDoc 注释,所以并不算真正的使用了 TypeScript

TypeScript 有几个规则可以让类型检查更加严格。如下:

// "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true,              /* Enable strict null checks. */
// "strictFunctionTypes": true,           /* Enable strict checking of function types. */
// "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

如果你一点点的迁移,那我建议你一个一个的开启。如果你倾向于一下子解决所有的问题,那么可以直接在 tsconfig.json 写:

"strict": true,

这样可以一次性打开所有严格设置。

如果有些库因为丢失类型而报错,那么我们就不能不为 Node.js 安装类型。
为这些库安装类型。也不是每个库都需要安装,大部分类型是有的。个别用 javascript 写的库,需要单独安装定义类型的脚本, 维护他们自己的类型。

比如为 ExpressJS 安装类型检查。

npm install -D @types/express

如果您只在项目内部使用类型,而不是自己开发一个库文件,那么您应该将这个包作为开发依赖项安装。 稍后我们举个例子。

当你开启严格模式之后,要记得修复问题。

修复后,还可以添加更多类型,使得代码更加健壮。比如,我想让我的 data route 的返回值更加明确,所以我加了一个新的类型—— Payload。引入了一个辅助的类型,去升级 src/routes/data.ts 里的类型。

import { Router, Request } from 'express';
import { Response } from 'express-serve-static-core';

type Payload = {
 hello: 'world'
}

export const routes = Router();

routes.get('/', (req: Request, res: Response<Payload>) => {
 res.send({
   hello: 'world',
 });
});

这个改变可以让我们更加确定的知道返回值是 { "hello": "world" }。如果不是,就会有报错提醒。你也可以改变类型定义 type Payload 定义,然后及时调整其他地方的引用。

有了 TypeScript 之后,代码会有越来越多的类型。TypeScript手册 和 type-fest 可以帮助你学习更多的方法。

使用 TypeScript 维护库

如果你想写一个公共库,与他人共享,那你可能迫不及待想使用 TypeScript 。 使用 TypeScript 的话,像 Visual Studio Code 这类编辑器可以自动从你的库里提取信息,像文件定义 (作为 TypeScript 被引用定义文件)。

声明有两种定义方式。第一种是在库中直接定义。在 package.json 中有 types 属性放在主要的 JavaScript 文件右侧。可以参考 TypeScript 文档

可以创建一个声明文件,在 tsconfig.json 里,设置 declarations 属性值为 true

{
 "compilerOptions": {
   "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
   "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
   "allowJs": true /* Allow javascript files to be compiled. */,
   "checkJs": true /* Report errors in .js files. */,
   "declaration": true /* Generates corresponding '.d.ts' file. */,
   "outDir": "./dist" /* Redirect output structure to the directory. */,
   "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
   "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,

   "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
 }
}

这样设置后,会在 JavaScript 里自动生成声明。**TypeScript 3.7 版本之后, allowJSdeclaration 属性可以合二为一。**也就是说,即使你在项目中只写了 JavaScript,基础的 TypeScript 声明文件也会自动生成,不需要自己维护。

另一种通过社区项目确定DefinitelyTyped。任何人都可以创建请求来添加或更新不自发布声明的库的声明文件。

TypeScript 为您效劳

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3UMYpgXk-1579141267622)(https://twilio-cms-prod.s3.amazonaws.com/original_images/cLhbD-Lb66ChhnMifUjOBDcxfKFxQM52pFTZsV2kRhBcz2VB5SgEOoTZgmeamgQB69gOou75_dS6cK)]

TypeScript 最终是一个为你服务的工具。你可以自己选择多用或者少用。它能帮助到你的,比你想象的要多。TypeScript 会尽可能猜测你写的代码是什么类型。但是有时,你还是需要自己去帮助自己。

希望 TypeScript 能够帮助到你的项目更加稳健的运行。我将 twilio-run 项目成功的从 JavaScript 移动到 TypeScript
如果你想看的案例的步骤,可以看这里

有任何问题,随时都可以联系我。我很高兴我可以帮助到你们。

原文链接:https://www.twilio.com/blog/move-to-typescript

你可能感兴趣的:(玲珑骰子安红豆,typescript)