譯者:張?zhí)燔?/p>
原文:Improving Development with TypeScript
本文為極客學(xué)院Wiki組織翻譯,轉(zhuǎn)載請(qǐng)注明出處。
時(shí)間:2016.3.21
世界在TypeScript的眼里是什么樣子呢?你在使用TypeScript versus ES6編程的時(shí)候有什么得失呢?
如果你一直在琢磨這個(gè)問題,那么今天我們將深入地幫你列出答案。解決這個(gè)問題的最好辦法是通過代碼。所以讓我們來深入了解它吧。在本文中,我們將改造 Kendo UI 的示例app中的其中一個(gè) - Layout Digram App。我選擇這個(gè)示例是因?yàn)樗烁鞣N排序的 Kendo UI 控制。由于我們很多人使用 Angular JS 開發(fā),我們將繼續(xù)并且從它的 jQuery 實(shí)現(xiàn)方式來重構(gòu)(如果你不使用 Angular ,這個(gè)示例仍然是可以參考的,簡(jiǎn)單忽略特定的 Angular 位)。
一個(gè)支持TypeScript的IDE。如下:
TSD(TYPESCRIPT DEFINITION MANAGER) 你需要下載所有的TypeScript definitions。這個(gè)包含了我們項(xiàng)目中使用的JavaScript庫的定義(AngularJS,lodash,KendoUI,等。)你可以把TSD命令行工具當(dāng)做其它依賴包工具的等價(jià)物,比如Nuget,Bower,npm,等等。
安裝TSD,你需要安裝Node.js/npm:
npm install tsd -g
你現(xiàn)在可以搜索并且安裝其它TypeScript包。TSD目前即包含JavaScript庫的客戶端定義,又包含JavaScript庫的服務(wù)端定義。
例如:
tsd query angular
結(jié)果如下(簡(jiǎn)化):
- 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
然后你可以根據(jù)自己需求自定義安裝:
tsd install angular --save
你可能會(huì)注意到TypeScript的定義文件已“d.ts”為后綴,比如 AngularJS 的 TypeScript定義文件 為 angular.d.ts 。在安裝一個(gè)定義文件的時(shí)候忽略 --save 選項(xiàng),如果文件不存在將創(chuàng)建一個(gè)tsd.d.ts文件,并且為我們項(xiàng)目中的每個(gè)TypeScript定義的依賴添加入口。
下面是運(yùn)行命令行的目錄結(jié)構(gòu)圖:
├── myapp/
│ ├── typings
│ │ ├── angularjs
│ │ │ ├── angular.d.ts
│ │ ├── tsd.d.ts
下面你會(huì)注意到,一條添加到Angular tsd依賴的命令是如何為我們 app/project 目錄下的 tsd.d.ts 提供其他的依賴的。
/// <reference path="express/express.d.ts" />
/// <reference path="node/node.d.ts" />
/// <reference path="stylus/stylus.d.ts" />
/// <reference path="serve-favicon/serve-favicon.d.ts" />
/// <reference path="morgan/morgan.d.ts" />
/// <reference path="body-parser/body-parser.d.ts" />
/// <reference path="errorhandler/errorhandler.d.ts" />
/// <reference path="serve-static/serve-static.d.ts" />
/// <reference path="mime/mime.d.ts" />
/// <reference path="../public/lib/kendo-ui/typescript/kendo.all.d.ts" />
/// <reference path="angularjs/angular.d.ts" />
/// <reference path="angular-ui-router/angular-ui-router.d.ts" />
/// <reference path="jquery/jquery.d.ts" />
你可能會(huì)注意到列表中包含了Kendo UI tsd。有時(shí)我們下載的例如Kendo UI,angular-ui-router,和其他包括tsd文件的JavaScript庫。在這些情況下,我們可以只打開tsd.d.ts文件,并且直接引用到我們項(xiàng)目app/project目錄(使用相對(duì)路徑)。
正如我前面所述,本文中,我們將使用AngularJS重構(gòu)Kendo UI Diagram Sample Application。這是一個(gè)很好的例子,因?yàn)樗诎咐齛pp中使用了很多 Kendo UI組件,允許我們通過TypeScript和AngularJS使用大量的Kendo UI組件。
在TypeScript中,有一個(gè)靜態(tài)輸入數(shù)據(jù)類型的概念,據(jù)我所知,大部分團(tuán)隊(duì)通常不管他們正在敲些什么,只是完全的限定。然而這篇文章中,我們將重命名大部分 Kendo UI,因而使他們根據(jù)簡(jiǎn)短。同樣,你也可以跳過命名空間步驟的別名,并且按你所想的,對(duì)所有都完全限定。
例如,我們使用完全限定的命名空間初始化一個(gè) ObservableArray :
var myArray = new kendo.data.ObservableArray([]);
那么,下面我們使用別名命名空間初始化一個(gè) ObservableArray
import ObserverableArray = kendo.data.ObservableArray; // aliased
var myArray = new ObserverableArray([]); // initialized w/ alias
下一步,我們繼續(xù)解決關(guān)注點(diǎn)分離。我們將從邏輯層和返回?cái)?shù)據(jù)的view model分離view(presentation),例如組件初始化和數(shù)據(jù)綁定。
作為一個(gè)最佳實(shí)踐,我習(xí)慣為每一個(gè) Angular Controller/ViewModel 創(chuàng)建一個(gè)接口,并且放在相同的文件中,作為 Controller的實(shí)現(xiàn)。為什么呢?這里有幾個(gè)重要的原因:
下面是IDiagramController接口(diagram.controller.ts):
interface IDiagramController {
diagramWidget: Diagram;
diagramWidgetOptions: IDiagramOptions;
canvasBackgroundColor: string;
selected: Array<any>;
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;
}
現(xiàn)在我們可以實(shí)現(xiàn)IDiagramController,我們盡量使用Controller/ViewModel,注意,下面,我們使用Angular注冊(cè)DiagramController的地方實(shí)現(xiàn)這個(gè)類。我也建議使用Angular 1.x版本,因?yàn)闊o論何時(shí)升級(jí)到Angular v2,都會(huì)很好的兼容。
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 的好處是所有的類都是類型安全的,而且提供了一個(gè)支持確定開發(fā)時(shí)和編譯時(shí)錯(cuò)誤提醒。當(dāng)完全由TypeScript編寫時(shí),當(dāng)你的類型是混合類型或者使用靜態(tài)語言例如C#,Java,C++,等等實(shí)現(xiàn)非靜態(tài)操作時(shí),你能夠獲取開發(fā)時(shí)和編譯時(shí)的錯(cuò)誤。
例如,如果你使用 Visual Studio Code,你好注意到,你能夠獲取接口沒有被馬上實(shí)現(xiàn)的警告,如果我們通過TypeScript獲取編譯錯(cuò)誤。實(shí)際上我們使用混合類型是相同的(例如使用number聲明,賦值string)。

下面TypeScript能夠從聲明中推斷出myArray是ObservableArray 的一種類型。然而我們?cè)O(shè)置myArray 為 ObservableObjectm TypeScript 會(huì)立即指示錯(cuò)誤位置。

讓我們來看看重構(gòu)代碼以支持新架構(gòu)的案例,首先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<input id='upload' type='file' name='files' />", encoded: false, spriteCssClass: "open-item", cssClass: "upload-item" },
{ text: "Save<a id='export' download='diagram.json'></a>", 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<input kendo-upload='upload' type='file' name='files' k-options='vm.uploadOptions' />", encoded: false, spriteCssClass: "open-item", cssClass: "upload-item" },
{ text: "Save<a id='export' download='diagram.json' ng-click='vm.exportClick($event)'></a>", 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);
}
};
這里,我們將重構(gòu)ShapeProperties變化事件,當(dāng)其中一個(gè)屬性(顏色,形狀等)改變時(shí),會(huì)同步更改選中對(duì)象的設(shè)計(jì)顯示。
首先,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版本:
<li>
<span>Background Color:</span>
<input kendo-color-picker
ng-model="vm.selectedShape.options.fill"
k-on-change="vm.shapePropertiesChange(kendoEvent)" />
</li>
<li>
<span>Stroke Color:</span>
<input
kendo-color-picker
ng-model="vm.selectedShape.options.stroke.color"
k-on-change="vm.shapePropertiesChange(kendoEvent)" />
</li>
<li>
<span>Stroke Width:</span>
<input kendo-numeric-text-box type="text"
k-ng-model="vm.selectedShape.options.stroke.width"
k-on-change="vm.shapePropertiesChange(kendoEvent)" />
</li>
<!-- code shortened for brevity-->
請(qǐng)看diagram.controller.ts,你會(huì)發(fā)現(xiàn)借助于Angular的MVVM優(yōu)勢(shì),我們?cè)僖膊恍枋褂胘Query selectors 去拼湊UI控件?,F(xiàn)在我們直接綁定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
);
}
});
};
你可能會(huì)注意到,我們使用到了TypeScript中ES6/ECMA6的新功能。例如,我們?cè)谏鲜鰂orEach方法(aka fat arrows 和lambdas)中使用了箭頭的功能。
隨著最近發(fā)布的TypeScript v1.7x,我們?nèi)缃裆踔聊軌蚴褂?a rel="nofollow" >ES7/ECMA7新功能開發(fā),這個(gè)新功能可以兼容很多瀏覽器,即使不支持ES6或ES7。
此外,即使是Kendo UI類型我們也可以獲取 Intellisense,因?yàn)槲覀兟暶髁藄electedShape 并且定義為Kendo UI Shape 類型。
var selectedShape: kendo.dataviz.diagram.Shape;

顯然,這將是和所有你導(dǎo)入的TSD庫類型相似的,包含了jQuary,Angular 和 loadsh。
此外,我們現(xiàn)在可以實(shí)現(xiàn)一個(gè)真正的“查找所有依賴” 或者“查找所有使用”,舉例來說,你能夠?yàn)槲覀冊(cè)赩iewModel或者Angular控制器中的selectedShape實(shí)現(xiàn)一個(gè)“查找所有依賴”。如果這是用于工程間,我們也能夠跨工程獲取結(jié)果列表。

如果你使用Visiual Studio Code,則能夠打開“peek”視圖,并且在右側(cè)列出所有使用this.selectedShape的列表。你能夠通過點(diǎn)擊瀏覽每一個(gè)出現(xiàn)的地方,而且通過右側(cè)的視圖列表瀏覽也能夠自動(dòng)的滾動(dòng)到出現(xiàn)的地方。

其它存在TypeScript特性的有:
值得注意的是,這些特不僅Visual Studio Code獨(dú)有的。在大多數(shù)支持TypeScript的IDE都可以使用。由于為JavaScript提供了強(qiáng)大的開發(fā)和編譯時(shí)體驗(yàn),因此TypeScrip帶來了很多好處,可以提高你的開發(fā)效率。
我已經(jīng)上傳了文中介紹用TypeScript和AngularJS重構(gòu)Kendo UI Diagram Application的sample到Github上。你可以訪問。我也已經(jīng)部署已完成和已經(jīng)重構(gòu)好的應(yīng)用,地址。
在接下來的文章中,我打算使用Kendo替換TypeScript和Angular2 TypeScript with NativeScript。可以通過Twitter@lelong37 給我意見反饋或者直接留言。
祝編碼愉快!