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文件选项的全部内容,但对于这篇文章来说,有趣的是allowJs ,checkJs ,和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知道事物应该是什么类型,输入
domConstruct或this.将为你提供一个特定于该类型的自动完成列表。
- 我们已经在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。