译者:张天军
原文:Improving Development with TypeScript
本文为极客学院Wiki组织翻译,转载请注明出处。
时间:2016.3.21
世界在TypeScript的眼里是什么样子呢?你在使用TypeScript versus ES6编程的时候有什么得失呢?
如果你一直在琢磨这个问题,那么今天我们将深入地帮你列出答案。解决这个问题的最好办法是通过代码。所以让我们来深入了解它吧。在本文中,我们将改造 Kendo UI 的示例app中的其中一个 - Layout Digram App。我选择这个示例是因为它包含了各种排序的 Kendo UI 控制。由于我们很多人使用 Angular JS 开发,我们将继续并且从它的 jQuery 实现方式来重构(如果你不使用 Angular ,这个示例仍然是可以参考的,简单忽略特定的 Angular 位)。
快速入门
环境:
Node.js(用npm安装);
-
一个支持TypeScript的IDE。如下:
- VSCode(我自己使用的IDE & 本文也是用这个 IDE)
- WebStrom
- Sublime(带 TypeScript 插件)
- Atom(带 TypeScript 插件)
- Visual Studio(所有版本)
- Eclipse
TSD(TypeScript 定义管理器 - 命令行工具)
TSD(TYPESCRIPT DEFINITION MANAGER)
你需要下载所有的TypeScript definitions。这个包含了我们项目中使用的JavaScript库的定义(AngularJS,lodash,KendoUI,等。)你可以把TSD命令行工具当做其它依赖包工具的等价物,比如Nuget,Bower,npm,等等。
安装TSD,你需要安装Node.js/npm:
npm install tsd -g
你现在可以搜索并且安装其它TypeScript包。TSD目前即包含JavaScript库的客户端定义,又包含JavaScript库的服务端定义。
例如:
tsd query angular
结果如下(简化):
- angular-agility / angular-agility
- angular-bootstrap-lightbox / angular-bootstrap-lightbox
- angular-dialog-service / angular-dialog-service
- angular-dynamic-locale / angular-dynamic-locale
- angular-file-upload / angular-file-upload
- angular-formly / angular-formly
- angular-gettext / angular-gettext
- angular-google-analytics / angular-google-analytics
- angular-growl-v2 / angular-growl-v2
- angular-hotkeys / angular-hotkeys
- angularjs / angular
然后你可以根据自己需求自定义安装:
tsd install angular --save
你可能会注意到TypeScript的定义文件已“d.ts”为后缀,比如 AngularJS 的 TypeScript定义文件 为 angular.d.ts 。在安装一个定义文件的时候忽略 --save 选项,如果文件不存在将创建一个tsd.d.ts文件,并且为我们项目中的每个TypeScript定义的依赖添加入口。
下面是运行命令行的目录结构图:
├── myapp/
│ ├── typings
│ │ ├── angularjs
│ │ │ ├── angular.d.ts
│ │ ├── tsd.d.ts
下面你会注意到,一条添加到Angular tsd依赖的命令是如何为我们 app/project 目录下的 tsd.d.ts 提供其他的依赖的。
///
///
///
///
///
///
///
///
///
///
///
///
///
你可能会注意到列表中包含了Kendo UI tsd。有时我们下载的例如Kendo UI,angular-ui-router,和其他包括tsd文件的JavaScript库。在这些情况下,我们可以只打开tsd.d.ts文件,并且直接引用到我们项目app/project目录(使用相对路径)。
开始编码
正如我前面所述,本文中,我们将使用AngularJS重构Kendo UI Diagram Sample Application。这是一个很好的例子,因为它在案例app中使用了很多 Kendo UI组件,允许我们通过TypeScript和AngularJS使用大量的Kendo UI组件。
别名(可选)
在TypeScript中,有一个静态输入数据类型的概念,据我所知,大部分团队通常不管他们正在敲些什么,只是完全的限定。然而这篇文章中,我们将重命名大部分 Kendo UI,因而使他们根据简短。同样,你也可以跳过命名空间步骤的别名,并且按你所想的,对所有都完全限定。
例如,我们使用完全限定的命名空间初始化一个 ObservableArray :
var myArray = new kendo.data.ObservableArray([]);
那么,下面我们使用别名命名空间初始化一个 ObservableArray
import ObserverableArray = kendo.data.ObservableArray; // aliased
var myArray = new ObserverableArray([]); // initialized w/ alias
下一步,我们继续解决关注点分离。我们将从逻辑层和返回数据的view model分离view(presentation),例如组件初始化和数据绑定。
TypeScript接口抽象(可选)
作为一个最佳实践,我习惯为每一个 Angular Controller/ViewModel 创建一个接口,并且放在相同的文件中,作为 Controller的实现。为什么呢?这里有几个重要的原因:
- ng Controller(class)的目的很明显,通过接口我们可以快速理解这个意图,用处和关注点。
- 理解什么是ng.IScope($scope)的界限。
下面是IDiagramController接口(diagram.controller.ts):
interface IDiagramController {
diagramWidget: Diagram;
diagramWidgetOptions: IDiagramOptions;
canvasBackgroundColor: string;
selected: Array;
selectedShape: Shape;
selectedConnection: Connection;
diagramZoomOptions: ISliderOptions;
menuOptions: IMenuOptions;
uploadOptions: IUploadOptions;
splitterOptions: ISplitterOptions;
panelBarOptions: IPointOptions;
colorPickerOptions: IColorPickerOptions;
canvasLayoutOptions: IDropDownListOptions;
connectionCapOptions: IDropDownListOptions;
windowWidgetOptions: IWindowOptions;
shapeItemDraggableOptions: IDraggableOptions;
alignConfigurationOptions: IButtonOptions;
arrangeConfigurationOptions: IButtonOptions;
windowWidget: Kwindow;
shapePropertiesChange: (e: JQuery) => void;
exportClick: (e: HTMLAnchorElement) => void;
}
现在我们可以实现IDiagramController,我们尽量使用Controller/ViewModel,注意,下面,我们使用Angular注册DiagramController的地方实现这个类。我也建议使用Angular 1.x版本,因为无论何时升级到Angular v2,都会很好的兼容。
class DiagramController implements IDiagramController {
static $inject = ['$scope', '$window'];
constructor(private $scope: IDiagramScope, private $window: any) {
var vm = this;
}
}
angular
.module('diagram')
.controller('diagram.DiagramController', DiagramController);
TypeScript开发时间和完美编译时间
TypeScript 的好处是所有的类都是类型安全的,而且提供了一个支持确定开发时和编译时错误提醒。当完全由TypeScript编写时,当你的类型是混合类型或者使用静态语言例如C#,Java,C++,等等实现非静态操作时,你能够获取开发时和编译时的错误。
例如,如果你使用 Visual Studio Code,你好注意到,你能够获取接口没有被马上实现的警告,如果我们通过TypeScript获取编译错误。实际上我们使用混合类型是相同的(例如使用number声明,赋值string)。
下面TypeScript能够从声明中推断出myArray是ObservableArray 的一种类型。然而我们设置myArray 为 ObservableObjectm TypeScript 会立即指示错误位置。
重构操作和KENDO MENU
让我们来看看重构代码以支持新架构的案例,首先jQuery 和JavaScript版本:
var actions = {
blank: reset,
undo: undo,
redo: redo,
copy: copyItem,
paste: pasteItem
};
$("#menu ul").kendoMenu({
dataSource: [
{ text: "New", spriteCssClass: "new-item", items: [
{ text: "Blank", spriteCssClass: "blank-item", cssClass: "active" }
]
},
{ text: "Open", encoded: false, spriteCssClass: "open-item", cssClass: "upload-item" },
{ text: "Save", encoded: false, spriteCssClass: "save-item" },
{ text: "Undo", spriteCssClass: "undo-item", cssClass: "active" },
{ text: "Redo", spriteCssClass: "redo-item", cssClass: "active" },
{ text: "Copy", spriteCssClass: "copy-item", cssClass: "active" },
{ text: "Paste", spriteCssClass: "paste-item", cssClass: "active" }
],
select: function(e) {
var item = $(e.item),
itemText = item.children(".k-link").text();
if (!item.hasClass("active")) {
return;
}
actions[itemText.charAt(0).toLowerCase() + itemText.slice(1)]();
}
});
与此相比,使用TypeScript和AngularJS 版本如下:
var actions: IMenuActions = {
blank: (e: IMenuSelectEvent): void => {
this.diagramWidget.clear();
},
undo: (e: IMenuSelectEvent): void => {
this.diagramWidget.undo();
},
redo: (e: IMenuSelectEvent): void => {
this.diagramWidget.redo();
},
copy: (e: IMenuSelectEvent): void => {
this.diagramWidget.copy();
},
paste: (e: IMenuSelectEvent): void => {
this.diagramWidget.paste();
}
};
vm.menuOptions = {
dataSource: [
{
text: "New", spriteCssClass: "new-item", items: [
{ text: "Blank", spriteCssClass: "blank-item", cssClass: "active" }
]
},
{ text: "Open", encoded: false, spriteCssClass: "open-item", cssClass: "upload-item" },
{ text: "Save", encoded: false, spriteCssClass: "save-item" },
{ text: "Undo", spriteCssClass: "undo-item", cssClass: "active" },
{ text: "Redo", spriteCssClass: "redo-item", cssClass: "active" },
{ text: "Copy", spriteCssClass: "copy-item", cssClass: "active" },
{ text: "Paste", spriteCssClass: "paste-item", cssClass: "active" }
],
select: (e: IMenuSelectEvent) => {
var item = angular.element(e.item),
itemText = item.children(".k-link").text();
if (!item.hasClass("active")) {
return;
}
actions[itemText.charAt(0).toLowerCase() + itemText.slice(1)](e);
}
};
重构SHAPEPROPERTIES变化事件
这里,我们将重构ShapeProperties变化事件,当其中一个属性(颜色,形状等)改变时,会同步更改选中对象的设计显示。
首先,jQuery和JavaScript版本:
$("#shapeProperties").on("change", shapePropertiesChange);
function shapePropertiesChange() {
var elements = selected || [],
options = {
fill: $("#shapeBackgroundColorPicker").getKendoColorPicker().value(),
stroke: {
color: $("#shapeStrokeColorPicker").getKendoColorPicker().value(),
width: $("#shapeStrokeWidth").getKendoNumericTextBox().value()
}
},
bounds = new Rect(
$("#shapePositionX").getKendoNumericTextBox().value(),
$("#shapePositionY").getKendoNumericTextBox().value(),
$("#shapeWidth").getKendoNumericTextBox().value(),
$("#shapeHeight").getKendoNumericTextBox().value()
),
element, i;
for (i = 0; i < elements.length; i++) {
element = elements[i];
if (element instanceof Shape) {
element.redraw(options);
element.bounds(bounds);
}
}
}
下面让我来看看 TypeScript和AngularJS版本:
Background Color:
Stroke Color:
Stroke Width:
请看diagram.controller.ts,你会发现借助于Angular的MVVM优势,我们再也不需使用jQuery selectors 去拼凑UI控件。现在我们直接绑定View到ViewModel:
public shapePropertiesChange = (e: JQuery): void => {
var elements = this.selected || [];
var i: number, element;
elements.forEach((element) => {
if (element instanceof Shape) {
var shape = element;
shape.redraw({
fill: this.selectedShape.options.fill,
stroke: this.selectedShape.options.stroke
});
shape.bounds(
this.selectedShape.height,
this.selectedShape.width,
this.selectedShape.x,
this.selectedShape.y
);
}
});
};
ES6 & ES7
你可能会注意到,我们使用到了TypeScript中ES6/ECMA6的新功能。例如,我们在上述forEach方法(aka fat arrows 和lambdas)中使用了箭头的功能。
随着最近发布的TypeScript v1.7x,我们如今甚至能够使用ES7/ECMA7新功能开发,这个新功能可以兼容很多浏览器,即使不支持ES6或ES7。
INTELLISENSE
此外,即使是Kendo UI类型我们也可以获取 Intellisense,因为我们声明了selectedShape 并且定义为Kendo UI Shape 类型。
var selectedShape: kendo.dataviz.diagram.Shape;
显然,这将是和所有你导入的TSD库类型相似的,包含了jQuary,Angular 和 loadsh。
此外,我们现在可以实现一个真正的“查找所有依赖” 或者“查找所有使用”,举例来说,你能够为我们在ViewModel或者Angular控制器中的selectedShape实现一个“查找所有依赖”。如果这是用于工程间,我们也能够跨工程获取结果列表。
如果你使用Visiual Studio Code,则能够打开“peek”视图,并且在右侧列出所有使用this.selectedShape的列表。你能够通过点击浏览每一个出现的地方,而且通过右侧的视图列表浏览也能够自动的滚动到出现的地方。
其它存在TypeScript特性的有:
- Solution-wide 重构
- Peek定义
- Go to定义
值得注意的是,这些特不仅Visual Studio Code独有的。在大多数支持TypeScript的IDE都可以使用。由于为JavaScript提供了强大的开发和编译时体验,因此TypeScrip带来了很多好处,可以提高你的开发效率。
总结
我已经上传了文中介绍用TypeScript和AngularJS重构Kendo UI Diagram Application的sample到Github上。你可以访问。我也已经部署已完成和已经重构好的应用,地址。
在接下来的文章中,我打算使用Kendo替换TypeScript和Angular2 TypeScript with NativeScript。可以通过Twitter@lelong37 给我意见反馈或者直接留言。
祝编码愉快!