使用TypeScript提高開發能力

譯者:張天軍

原文: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 位)。

快速入門

環境:

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 提供其他的依賴的。

/// <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" />

你可能會注意到列表中包含了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<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;
}

現在我們可以實現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)。

Paste_Image.png

下面TypeScript能夠從聲明中推斷出myArray是ObservableArray 的一種類型。然而我們設置myArray 為 ObservableObjectm TypeScript 會立即指示錯誤位置。

Paste_Image.png

重構操作和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<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);
    }
};

重構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版本:


    <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-->

請看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;
Paste_Image.png

顯然,這將是和所有你導入的TSD庫類型相似的,包含了jQuary,Angular 和 loadsh。

此外,我們現在可以實現一個真正的“查找所有依賴” 或者“查找所有使用”,舉例來說,你能夠為我們在ViewModel或者Angular控制器中的selectedShape實現一個“查找所有依賴”。如果這是用于工程間,我們也能夠跨工程獲取結果列表。

Paste_Image.png

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

Paste_Image.png

其它存在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 給我意見反饋或者直接留言。

祝編碼愉快!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內容

  • 1、angularjs的幾大特性是什么? 雙向數據綁定、依賴注入、模板、指令、MVC/MVVM 2、列舉幾種常見的...
    2e9a10d418ab閱讀 1,368評論 0 10
  • 如果你想了解如何將現有的AngularJs 1.X(簡稱ng1)的項目平滑升級至Angular 2.X(簡稱ng2...
    摩卡秀閱讀 1,378評論 0 2
  • 1、angularjs的幾大特性是什么? 雙向數據綁定、依賴注入、模板、指令、MVC/MVVM 2、列舉幾種常見的...
    秀才JaneBook閱讀 1,551評論 0 22
  • 前幾天,接到了閨蜜D的電話,聲音中掩飾不住的氣急敗壞和無可奈何。她說:為什么總感覺生活得很匆忙?明明沒有太多事情,...
    冬羽閱讀 964評論 0 0
  • 對自己懷有何種期待,決定著我們將怎樣的活著。 對自己的期待理應來自我,但更多是為外部期許而戰?;蛟S是父母妻兒親朋,...
    荒劍離閱讀 721評論 0 2