在Dojo工具包的应用中逐步采用TypeScript的详细指南

312 阅读9分钟

TypeScript已经成为现代网络开发库的主流。如果没有某种类型的指导,消耗由第三方编写的函数和小部件可能很容易出错。在接口中引入静态类型,不仅可以减少误操作,而且还有包括智能代码完成在内的额外好处。

Dojo Toolkit是最早的促进构建大型、动态、互连的单页应用程序的库之一。除了用于加载和存储数据、管理事件和处理DOM的工具外,它还通过Dijit提供了一套多样化的小工具。

尽管Dojo工具包是在TypeScript出现之前编写的,但TypeScript是否可以从中受益?对我们来说,幸运的是,TypeScript的设计者已经通过我们将在这篇文章中看到的工具使之成为可能。

将 TypeScript 引入您的项目

为了帮助我们的项目从 JavaScript 发展到 TypeScript,我们需要一个好的起点。看看我们将在这篇文章中使用的Dojo Toolkit应用程序的例子

我们只需要两样东西来开始将 TypeScript 集成到我们的项目中:TypeScript 编译器和配置文件。

安装TypeScript编译器

我们将使用 TypeScript 编译器来执行编译时类型检查,并最终将 TypeScript 文件转化为 JavaScript。

安装TypeScript编译器很容易,我们只需将TypeScript安装为devDependency

npm i -D typescript

TypeScript 配置文件

现在TypeScript编译器已经安装完毕,我们需要将其配置为与我们的应用程序一起工作。在项目根目录下创建一个名为tsconfig.json 的新文件,内容如下:

{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "esModuleInterop": true,
    “noImplicitThis”: true,
    "lib": [
      "dom",
      "es2015",
      "scripthost"
    ],
    "module": "amd",
    "outDir": "build/src/demo/",
    "removeComments": false,
    "target": "es5"
  },
  "include": [
    "./src/**/*.js",
    "./src/**/*.ts"
  ]
}

你可以阅读关于tsconfig.json文件选项的全部内容,但对于这篇文章来说,有趣的是allowJscheckJs ,和esModuleInterop

  • allowJs - 允许对JavaScript文件进行编译。我们现在需要这个,因为我们的整个应用程序是用JavaScript写的。
  • checkJs - 报告JavaScript文件中的错误。我们可以让TypeScript对我们的原始JavaScript文件进行类型检查,可能会在我们进行任何代码修改之前就浮现出一些应用程序的错误。
  • esModuleInterop - 实现与dojo加载器的兼容。

有了这两个部分,我们现在可以尝试使用TypeScript编译器来编译我们的项目了。从你的项目根部,运行编译器:

./node_modules/.bin/tsc

啊哦!看起来我们有一些错误:

src/demo/index.js:1:1 - error TS2304: Cannot find name 'define'.

1 define([
  ~~~~~~

src/demo/widgets/Hello.js:1:1 - error TS2304: Cannot find name 'define'.

1 define([
  ~~~~~~

TypeScript对Dojo工具包一无所知,所以它不知道该如何进行。

环境声明

TypeScript被设计为与现有的JavaScript库一起使用。要做到这一点,你需要告诉它一些关于这些库的信息。这些类型的定义被称为 "环境声明",通常以.d.ts结束。环境声明不产生任何代码输出,而是简单地告知TypeScript关于其他代码的类型和API。Dojo工具包以dojo-typings包的形式提供环境声明。只需将此包安装为devDependency

npm i -D dojo-typings

然后,通过将这些文件添加到include 关键中,将其纳入你的tsconfig.json 文件:

{
 "include": [
    "./node_modules/dojo-typings/dojo/1.11/modules.d.ts",
    "./node_modules/dojo-typings/dijit/1.11/modules.d.ts",
    "./node_modules/dojo-typings/dojo/1.11/loader.d.ts",
    "./src/**/*.js",
    "./src/**/*.ts"
  ]
}

注意,我们已经包括了三种不同的环境声明(.d.ts文件):

  • dojo/1.11/modules.d.ts - Dojo 1.11的环境声明
  • diijt/1.11/modules.d.ts - 适用于Dijit 1.11的环境声明
  • dojo/1.11/loader.d.ts - Ambient declarations for the loader to give us access to require and define.

现在运行TypeScript编译器应该会有更好的结果:

./node_modules/.bin/tsc

成功了!我们现在已经为Dojo工具包实现了一些基本的TypeScript类型检查,但很快我们就会看到类型检查的力量,当我们的项目过渡到TypeScript时。

转移加载器

TypeScript不理解Dojo的模块加载器,所以我们需要将我们的代码过渡到使用ES模块语法来代替。然后,TypeScript将能够查看我们的导入并确定它们的类型。在我们的例子中,它将能够执行Button、TextBox等的类型。

看一下Hello.js 的顶部:

define([
    "dojo/_base/declare",
    "dojo/dom-construct",
    "dijit/_WidgetBase",
    "dijit/form/Button",
    "dijit/form/TextBox"
], function(
    declare,
    domConstruct,
    _WidgetBase,
    Button,
    TextBox
) {
    return declare([_WidgetBase], {
    // ...

使用ES模块的语法,这将看起来像:

import declare from "dojo/_base/declare";
import domConstruct from "dojo/dom-construct";
import _WidgetBase from "dijit/_WidgetBase";
import Button from "dijit/form/Button";
import TextBox from "dijit/form/TextBox";

export default declare([_WidgetBase], {

你想知道这怎么能与Dojo Toolkit兼容?继续编译这个项目(你会看到一些错误,但我们稍后会处理这些错误),然后打开build/src/demo/widgets/Hello.js

var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
define([
    "require", 
    "exports", 
    "dojo/_base/declare", 
    "dojo/dom-construct", 
    "dijit/_WidgetBase", 
    "dijit/form/Button", 
    "dijit/form/TextBox"
], function (require, exports, declare_1, dom_construct_1, _WidgetBase_1, Button_1, TextBox_1) {
// ...

你会马上注意到,我们的导入变成了对define的调用,看起来很像我们以前的define调用!TypeScript知道我们要使用AMD模块(来自我们的tsconfig.json 文件),并自动将我们的导入转化为适当的定义调用。这是一些强大的东西,就在那里。

类型安全

有了我们的导入,TypeScript可以读取类型并执行一些类型安全规则。你可能已经注意到,我们现在已经有了编译错误:

因为TypeScript知道我们的widget扩展自_WidgetBase ,它知道哪些属性是可用的,哪些是不可用的。如果我们试图使用一个不属于_WidgetBase 或我们扩展的属性,我们会看到一个关于该属性不存在的错误--就像我们在nameInput 。为了解决这个问题,我们只需要定义nameInput 属性并告诉TypeScript(通过JsDoc)它是什么类型。把这个添加到buildRendering 属性上面:

nameInput: /** @type TextBox */ (undefined),

在解决了我们的构建错误后,让我们快速盘点一下我们现在在Hello.js 中的类型安全:

  • 不小心使用了一个不存在的变量会产生一个编译时错误。因此,如果我们错误地输入了this.nmeInput ,我们就会立即得到一个错误。

  • 我们已经有了自动完成功能(大多数IDE都支持这个功能)!现在TypeScript知道事物应该是什么类型,输入domConstructthis.将为你提供一个特定于该类型的自动完成列表。

  • 我们已经在Dijit构造函数参数上得到了额外的类型安全。由于TypeScript知道TextBox({ ... }) 应该接受什么,如果你试图输入一个没有在类型中定义的属性,你会得到一个编译时错误。

这些都是巨大的胜利,但我们是否可以将我们的JavaScript文件迁移到完全的TypeScript文件,以获得更大的回报?

.js到.ts

对JavaScript文件进行类型检查是一个强大的功能,但最终你会想充分利用TypeScript所提供的一切。TypeScript文件不依赖JsDoc,并提供更多的表达式类型,允许编译器进一步分析你的代码。你的最终目标应该是将你的JavaScript代码库过渡到TypeScript优先的代码库。

重命名你的文件

第一步是将你的文件重命名为.ts而不是.js。一旦 TypeScript 看到 .ts 扩展名,它就知道它可以期待特殊的 TypeScript 语法,并对你的代码做出某些假设。将Hello.js 重命名为Hello.ts ,开始转换。你会注意到项目仍然在构建,尽管我们除了文件扩展名之外什么都没有改变。这是因为.ts文件尽量向后兼容.js文件。

更新你的类型

对于JavaScript文件,我们使用JsDoc来指定类型。对于TypeScript文件,我们将能够使用TypeScript特定的语法。让我们更新我们的nameInput 属性,通过TypeScript进行类型化。

nameInput: undefined as TextBox | undefined

在这里,我们声明nameInput 是未定义的,我们期望它包含什么(未定义)或TextBox的实例。这是一个简单的例子,但TypeScript提供了一个令人难以置信的广泛和先进的类型集,现在可以由你支配。

你的项目首先是什么?

将我们的示例应用程序转换为TypeScript是一件事,但你如何转换一个真实世界的项目。甚至从哪里开始呢?

关键业务逻辑

有些模块比其他模块更重要。当你有一个关键的、不应该被误用的模块时,将该模块转换为TypeScript是一个很好的方法,可以强制该模块被正确使用。

依赖关系少的模块

依赖关系少的模块是开始转换为 TypeScript 的一个好地方。为了使用ES导入语法,TypeScript需要知道被导入的模块。这意味着它要么需要有环境声明,要么已经是一个 TypeScript 文件。

潜在的问题

映射库

你可能正在使用一个你无法找到类型的第三方库。在这种情况下,你可能不得不自己编写环境声明。编写这些东西不在本篇文章的范围之内,但你可以找到很多关于这个问题的教程。从小处着手,只实现你正在使用的API的部分,然后再从那里建立。

接下来的步骤

将你的项目升级到TypeScript是使用现代网络技术的一个重要步骤。一旦成功,你可能想考虑更进一步。

TypeScript 和 ESLint

你可以使用ESLint和TypeScript一起执行额外的检查,这只是因为TypeScript添加的额外类型信息。例如,其中一些规则可以保证你没有指定一个你没有的类型,或者你总是要指定一个类型,你不应该依赖推断的类型。

Webpack

TypeScript编译器将把你的每个TypeScript文件作为单独的JavaScript文件输出。虽然这对你现有的构建系统来说是可以的,但在浏览器上加载所有这些单独的文件可能是低效的。Webpack可以处理你的TypeScript(和JavaScript)文件,并创建一个或多个捆绑版本,提供给你的用户。这些捆绑文件比加载单个文件更小、更高效。通过一些额外的插件,Webpack可以使用TypeScript编译器,而无需在构建过程中创建中间的JavaScript文件。甚至还有一个dojo-webpack插件,可以使用Webpack构建Dojo Toolkit应用程序。

总结

在这篇文章中,我们已经看到了如何将Dojo Toolkit和Dijit项目逐步转换为TypeScript项目。我们也看到了TypeScript的一些优势,以及我们可以从这里开始。希望这篇文章能激励你开始将你自己的项目转换为TypeScript。