TypeScript

TypeScript 提升您 JavaScript 的投資報酬率

Bill Wagner

下載代碼示例

手稿程式設計語言是實際上的 JavaScript 的超集合。 如果您使用 JavaScript,你已經寫手稿。 這並不意味著你寫好的手稿和使其所有功能的使用。 它意味著你有一個平滑遷移路徑從您現有的 JavaScript 對手稿的投資基本代碼利用了手稿提供的新功能。

在本文中,我會從 JavaScript 的應用程式遷移到手稿提供建議。 您將學習如何將移動從 JavaScript 到手稿使用的手稿類型系統來説明您編寫更好的代碼。 與手稿靜態分析,你會儘量減少錯誤和會更有成效。 通過遵循這些建議,還會在遷移期間儘量錯誤和警告的手稿類型系統從的量。

我會從開始管理通訊錄作為應用程式範例。 它是單頁面應用程式 (SPA) 使用 JavaScript 在用戶端上。 我一直為這篇文章簡單,僅包含顯示連絡人清單中的部分。 它使用的角的框架為綁定的資料和應用程式的支援。 角框架處理的資料繫結和顯示的連絡人資訊範本化。

三個 JavaScript 檔組成應用程式:App.js 檔包含啟動該應用程式的代碼。 ContactsController.js 檔是控制器的清單頁面。 ContactsData.js 檔包含將顯示的連絡人清單中。 控制器 — — 角的框架 — — 處理清單頁面的行為。 您可以對連絡人進行排序和顯示或隱藏任何單個連絡人的連絡人詳細資訊。 ContactsData.js 檔是連絡人的硬編碼設置。 在生產應用程式中,此檔將包含代碼來調用伺服器並檢索資料。 連絡人的硬編碼清單使得更自包含的演示。

Don擔心,如果你沒有太多經驗與角。 您將看到它是多麼容易使用,我開始將遷移應用程式。 應用程式遵循角的約定,很容易就可以保留您應用程式遷移到手稿。

開始遷移到手稿的應用程式的最佳地點是與控制器檔。 因為任何有效的 JavaScript 代碼也是有效的手稿代碼,只需更改到.ts 從.js 調用 contactsController.js 的控制器檔的副檔名。 手稿語言是一流公民Visual Studio2013年更新 2 中。 如果您有安裝的 Web 要點副檔名,您會看到手稿原始程式碼和生成的 JavaScript 輸出在同一視窗中 (請參閱圖 1)。

The TypeScript Editing Experience in Visual Studio 2013 Update 2
圖 1 在Visual Studio2013年更新 2 中手稿編輯經驗

因為填寫特定的語言功能未被使用,然而,這兩種觀點都幾乎相同。 在結束了的額外注釋行提供資訊Visual Studio調試手稿應用程式時。 應用程式使用Visual Studio,可以在手稿一級,而不是生成 JavaScript 原始程式碼級調試。

您可以看到,手稿編譯器報告錯誤為此應用程式,即使編譯器生成有效的 JavaScript 輸出。 這是偉大的手稿語言的功能之一。 它是規則的手稿是規則的 JavaScript 的嚴格超集合的自然後果。 我還沒尚未宣佈任何手稿檔中的符號 contactsApp。 因此,手稿編譯器假定的任何類型,並假定該符號將在運行時引用的物件。 儘管這些錯誤,我可以運行該應用程式,它仍將正常工作。

我可以繼續,更改的應用程式中的所有 JavaScript 檔的副檔名。 但我不建議做,只,因為將會有更多的錯誤。 應用程式仍然會工作,但有這麼多錯誤會更難使用手稿系統説明您編寫更好的代碼。 我更喜歡在一段時間處理一個檔並將類型資訊添加到應用程式,如我去。 這種方式我有數量較少的類型系統錯誤要修復一次。 我有一個清潔的生成後,我知道手稿編譯器説明我避免這些錯誤。

它很容易聲明為 contactsApp 的外部變數。 預設情況下,它將具有的任何類型:

declare var contactsApp: any;

雖然能夠修復編譯器錯誤,但它不會説明避免角庫中調用方法時出現錯誤。 任何類型是只是它聽起來像:什麼都有可能。 手稿不會執行任何類型檢查當您訪問 contactsApp 變數。 若要獲取類型檢查,您需要告訴填寫有關的 contactsApp 的類型和角框架中定義的類型。

手稿與一種稱為類型定義功能使現有的 JavaScript 庫的類型資訊。 一個類型定義是一組聲明沒有實現。 他們描述的類型和其對手稿編譯器的 Api。 DefinitelyTyped 專案在 GitHub 上的有很多流行的 JavaScript 庫,包括 Angular.js 的類型定義。 我在使用 NuGet 包管理器的專案中包括這些定義。

一旦列入了角的庫的類型定義,我可以使用它們來修復編譯器錯誤我看到。 我需要引用我只是添加到專案中的類型資訊。有告訴手稿編譯器為參考型別資訊的特別注釋:

/// <reference path="../Scripts/typings/angularjs/angular.d.ts" />

手稿編譯器現在可以解釋任何類型定義檔 angular.d.ts 中定義的類型。 是時候要修復的 contactsApp 變數的類型。 預期的類型的 contactsApp 變數,在 app.js 吳命名空間中聲明的是 IModule:

declare var contactsApp: ng.IModule;

這項聲明,我會把IntelliSense,每當在 contactsApp 後按下了一段時間。 我也會從手稿編譯器每當我拼錯或誤用 Api 的 contactsApp 物件的聲明得到錯誤報表。 編譯器錯誤不見了,我已經包括的應用程式物件的靜態類型資訊。

ContactsController 物件中代碼的其餘部分仍然缺乏的類型資訊。 直到你添加類型注釋,手稿編譯器將假定任何變數是任何類型。 ContactsApp.controller 方法的第二個參數是一個函數,該函數的第一個參數,$scope,是類型吳議員。IScope。 所以我會包含該類型在函式宣告上的 (contactData 將仍被解釋為任何類型):

contactsApp.controller('ContactsController',
  function ContactsController($scope : ng.IScope, contactData) {
    $scope.sortOrder = 'last';
    $scope.hideMessage = "Hide Details";
    $scope.showMessage = "Show Details";
    $scope.contacts = contactData.getContacts();
    $scope.toggleShowDetails = function (contact) {
      contact.showDetails = !contact.showDetails;
    }
  });

這就帶來了一套新的編譯器錯誤。 新的錯誤是因為裡面那個 contactsController 函數代碼操作不是吳的一部分的屬性。IScope 類型。 Ng.IScope 是一個介面,和實際的 $scope 物件是實現 IScope 應用程式-特定類型。 被操縱這些屬性是該類型的成員。 要利用手稿靜態類型,需要定義該應用程式-特定類型。 我就叫他 IContactsScope:

interface IContactsScope extends ng.IScope {
  sortOrder: string;
  hideMessage: string;
  showMessage: string;
  contacts: any;
  toggleShowDetails: (contact: any) => boolean;
}

一旦定義了介面,只需更改 $scope 變數在函式宣告中的類型:

function ContactsController($scope : IContactsScope, contactData) {

之後進行這些更改,可以生成的應用程式沒有錯誤,它將正確運行。 有幾個重要的概念,以觀察時添加此介面。 我沒有找到任何其他代碼並聲明任何特定類型實現的 IContactsScope 類型的通知。 手稿支援結構打字、 口語被稱為"鴨子類型化"。這意味著在 IContactsScope 中聲明任何聲明的屬性和方法的物件實現 IContactsScope 介面,介面的型別宣告它實現 IContactsScope。

看到,我使用的任何手稿類型作為 IContactsScope 的定義中的預留位置。 連絡人屬性工作表示的連絡人清單和我沒尚未遷移連絡人類型。 我可以使用任何作為預留位置和手稿編譯器不會執行任何類型檢查訪問這些值。 這是很有用的技術,在整個應用程式的遷移。

任何類型表示我沒尚未從 JavaScript 遷移到手稿的任何類型。 它使遷移更順利地與較少的錯誤從手稿編譯器在每次反覆運算中修復。 我也可以搜索變數聲明為任何類型和找到我仍然必須做的工作。 "任何"告訴手稿編譯器不執行任何檢查對該變數的類型。 什麼都有可能。 編譯器將假定您知道該變數上可用的 Api。 這並不意味著每次使用的"任何"是壞的。 有有效用途的任何類型,例如當一個 JavaScript API 旨在與不同類型的物件一起工作。 使用"任何"作為預留位置在遷移過程中是只是一個很好的形式。

最後,宣言 》 toggleShowDetails 顯示函式宣告在手稿中的表示方式:

toggleShowDetails: (contact: any) => boolean;

函數名稱是 toggleShowDetails。 在冒號後您將看到的參數清單。 此函數採用單一類型的參數,當前的任何。 "連絡人"的名稱是可選的。 您可以使用此向其他程式師提供更多資料。 脂肪箭頭指向的返回類型,是在此示例中的布林值。

介紹了在 IContactScope 定義的任何類型顯示你到哪裡去下一步工作。 手稿可以説明您避免錯誤,當你給它的類型與你工作有關的詳細資訊。 我會替換任何具有更好的什麼是在連絡人中通過定義 IContact 類型,其中包括可用連絡人物件上的屬性定義 (見圖 2)。

圖 2 包括接觸的物件上的屬性

interface IContact {
  first: string;
  last: string;
  address: string;
  city: string;
  state: string;
  zipCode: number;
  cellPhone: number;
  homePhone: number;
  workPhone: number;
  showDetails: boolean
}

與現在定義的 IContact 介面,我就會使用它在 IContactScope 介面中:

interface IContactsScope extends ng.IScope {
  sortOrder: string;
  hideMessage: string;
  showMessage: string;
  contacts: IContact[];
  toggleShowDetails: (contact: IContact) => boolean;
}

我不需要在 contactsController 函數中定義的 toggleShowDetails 函數的定義上添加類型資訊。 因為 $scope 變數是 IContactsScope,手稿編譯器知道分配到 toggleShowDetails 的函數必須與匹配函數原型在 IContactScope 中定義和參數必須是 IContact。

看看對於此版本的在 contactsController 生成 JavaScript 圖 3。 注意所有已從生成 JavaScript 刪除我已定義的介面類別型。 類型批註存在為你和靜態分析工具。 這些注釋不要攜帶向生成 JavaScript,因為他們是不需要。

圖 3 中的控制器和生成的 JavaScript 的手稿版本

/// reference path="../Scripts/typings/angularjs/angular.d.ts"
var contactsApp: ng.IModule;
interface IContact {
  first: string;
  last: string;
  address: string;
  city: string;
  state: string;
  zipCode: number;
  cellPhone: number;
  homePhone: number;
  workPhone: number;
  showDetails: boolean
}
interface IContactsScope extends ng.IScope {
  sortOrder: string;
  hideMessage: string;
  showMessage: string;
  contacts: IContact[];
  toggleShowDetails: (contact: IContact) => boolean;
}
contactsApp.controller('ContactsController',
  function ContactsController($scope : IContactsScope, contactData) {
    $scope.sortOrder = 'last';
    $scope.hideMessage = "Hide Details";
    $scope.showMessage = "Show Details";
    $scope.contacts = contactData.getContacts();
    $scope.toggleShowDetails = function (contact) {
      contact.showDetails = !contact.showDetails;
      return contact.showDetails;
    }
  });
// Generated JavaScript
/// reference path="../Scripts/typings/angularjs/angular.d.ts"
var contactsApp;
contactsApp.controller('ContactsController',
  function ContactsController($scope, contactData) {
  $scope.sortOrder = 'last';
  $scope.hideMessage = "Hide Details";
  $scope.showMessage = "Show Details";
  $scope.contacts = contactData.getContacts();
  $scope.toggleShowDetails = function (contact) {
    contact.showDetails = !contact.showDetails;
    return contact.showDetails;
  };
});
//# sourceMappingURL=contactsController.js.map

添加模組和類定義

將類型批註添加到您的代碼使靜態分析工具來查找和報告關於你已經在您的代碼中可能存在的錯誤。 這包括一切從IntelliSense和林特樣分析、 編譯時錯誤和警告。

在 JavaScript 提供了手稿的另一個主要優勢是範圍類型更好的語法。 手稿 module 關鍵字,可以將類型定義放在一個範圍內,避免與其他可能使用相同的名稱的模組中的類型的碰撞。

連絡人應用程式範例並不是那麼大,但它仍然是個好主意,要將類型定義放置在模組,以避免碰撞。 在這裡,我會將 contactsController 和我定義了一個名為的名片夾裡模組內的其他類型:

module Rolodex {
  // Elided
}

我還沒上任何定義在此模組中添加匯出關鍵字。 這意味著在該模組內只能從引用的名片夾裡模組內部定義的類型。 我會在此模組中定義的介面上添加匯出關鍵字,後來隨著我遷移 contactsData 代碼使用這些類型。 我也會為一類從一個函數 ContactsController 來更改代碼。 此類需要一個建構函式來初始化自身,但沒有任何其他公共方法 (請參閱圖 4)。

圖 4 更改 ContactsController 從函數中返回到類

export class ContactsController {
  constructor($scope: IContactsScope, contactData: any) {
    $scope.sortOrder = 'last';
    $scope.hideMessage = "Hide Details";
    $scope.showMessage = "Show Details";
    $scope.contacts = contactData.getContacts();
    $scope.toggleShowDetails = function (contact) {
      contact.showDetails = !contact.showDetails;
      return contact.showDetails;
    }
  }
}

現在創建這種類型的更改對 contactsApp.controller 的調用。 現在第二個參數是類類型,不是在先前定義的函數。 控制器函數的第一個參數是控制器的名稱。 角將控制器名稱映射到建構函式。 任何地方在 HTML 頁中引用 ContactsController 類型時,角將調用建構函式的 ContactsController 類:

contactsApp.controller('ContactsController', Rolodex.ContactsController);

控制器類型已現在已完全遷移從 JavaScript 的手稿。 新版本包含類型 anno­的一切: 定義或使用在控制器中。 在填寫,可以這樣無需在應用程式的其他部分中的更改。 沒有其他檔受到影響。 混合使用 JavaScript 手稿是光滑,從而簡化了將手稿添加到現有的 JavaScript 應用程式。 手稿類型系統依靠型別推斷和結構性打字、 方便容易手稿和 JavaScript 之間的相互作用。

現在,我要搬到 contactData.js 檔 (請參閱圖 5)。 此函數使用角工廠方法返回一個物件,返回的連絡人清單。 像控制器、 工廠方法將名稱 (contactData) 映射到一個函數,返回的服務。 此約定用於在建構函式中的控制器。 第二個參數的建構函式被命名為 contactData。 角使用該參數名稱映射到適當的工廠。 正如您所看到的角框架是基於公約 》。

圖 5 JavaScript 版本的 contactData 服務

'use strict';
contactsApp.factory('contactData', function () {
  var contacts = [
    {
      first: "Tom",
      last: "Riddle",
      address: "66 Shack St",
      city: "Little Hangleton",
      state: "Mississippi",
      zipCode: 54565,
      cellPhone: 6543654321,
      homePhone: 4532332133,
      workPhone: 6663420666
    },
    {
      first: "Antonin",
      last: "Dolohov",
      address: "28 Kaban Ln",
      city: "Gideon",
      state: "Arkensas",
      zipCode: 98767,
      cellPhone: 4443332222,
      homePhone: 5556667777,
      workPhone: 9897876765
    },
    {
      first: "Evan",
      last: "Rosier",
      address: "28 Dominion Ave",
      city: "Notting",
      state: "New Jersey",
      zipCode: 23432,
      cellPhone: 1232343456,
      homePhone: 4432215565,
      workPhone: 3454321234
    }
  ];
  return {
    getContacts: function () {
      return contacts;
    },
    addContact: function(contact){
      contacts.push(contact);
      return contacts;
    }
  };
})

再次,第一步是簡單地將從.js 的副檔名更改為.ts。 乾淨地編譯和生成的 JavaScript 密切匹配源記錄檔。 下一步,我把名片夾的同一模組中的 contactData.ts 檔中的代碼。 範圍為應用程式在相同的邏輯分區中的所有代碼。

下一步,我會將 contactData 工廠遷移到一個類。 將類聲明為類型 ContactDataServer。 而不是一個函數,返回一個物件具有兩個屬性的方法,我現在可以簡單地定義方法作為 ContactDataServer 的物件的成員。 初始資料現在是物件的 ContactDataServer 類型的資料成員。 我還需要在對 contactsApp.factory 的調用中使用此類型:

contactsApp.factory('contactsData', () => 
  new Rolodex.ContactDataServer());

第二個參數是一個函數,返回一個新的連絡人­DataServer。 我需要它的時候,工廠將創建該物件。 如果我嘗試編譯和運行此版本,我會編譯器錯誤因為 ContactDataServer 類型不從名片夾模組匯出。 引用它,但是,在調用到連絡人中­App.factory。 這是另一個例子如何填寫類型系統相當寬容的這使得遷移任務容易得多。 我很容易可以通過向 ContactDataServer 類聲明中添加匯出關鍵字來修復此錯誤。

你可以看到中的最終版本圖 6。 請注意我的 addContact 方法添加連絡人和輸入的參數的陣列的類型資訊。 都是可選的類型批註 — — 它是沒有他們的有效手稿。 但是,我鼓勵你向您填寫代碼添加必要的類型的所有資訊,因為它可以説明您避免的錯誤在手稿系統中,這將允許您將會更有成效。

圖 6 的手稿版本的 ContactDataServer

/// reference path="../Scripts/typings/angularjs/angular.d.ts"
var contactsApp: ng.IModule;
module Rolodex {
  export class ContactDataServer {
    contacts: IContact[] = [
      {
        first: "Tom",
        last: "Riddle",
        address: "66 Shack St",
        city: "Little Hangleton",
        state: "Mississippi",
        zipCode: 54565,
        cellPhone: 6543654321,
        homePhone: 4532332133,
        workPhone: 6663420666,
        showDetails: true
      },
      {
        first: "Antonin",
        last: "Dolohov",
        address: "28 Kaban Ln",
        city: "Gideon",
        state: "Arkensas",
        zipCode: 98767,
        cellPhone: 4443332222,
        homePhone: 5556667777,
        workPhone: 9897876765,
        showDetails: true
      },
      {
        first: "Evan",
        last: "Rosier",
        address: "28 Dominion Ave",
        city: "Notting",
        state: "New Jersey",
        zipCode: 23432,
        cellPhone: 1232343456,
        homePhone: 4432215565,
        workPhone: 3454321234,
        showDetails: true
      }
    ];
    getContacts() {
      return this.contacts;
    }
    addContact(contact: IContact) {
      this.contacts.push(contact);
      return this.contacts;
    }
  }
}
contactsApp.factory('contactsData', () => 
  new Rolodex.ContactDataServer());

現在,我已經創建了一個新的 ContactDataServer 類,可以與一個最後更改到控制器。 還記得 contactsController 建構函式的第二個參數是資料伺服器。 現在,我可以讓更多型別安全通過聲明下面的參數必須是 ContactDataServer 類型的:

constructor($scope: IContactsScope, contactData: ContactDataServer) {

順利 JavaScript 到手稿的遷移

手稿有很多更多的功能,比那些我已經表現出在這裡。 手稿與工作時,你會接受它的能力。 越多你使用 JavaScript 手稿擴展,您的工作效率會增加越多。 請記住手稿類型批註旨在提供平滑的遷移從 JavaScript 到手稿。 最重要的是,要記住手稿是 JavaScript 的嚴格超集合。 這意味著任何有效的 JavaScript 是一種有效的手稿。

同時,手稿類型批註有非常小的儀式。 鍵入批註哪裡您為他們提供和不讓你不得不將它們添加到處檢查。 你從 JavaScript 遷移到手稿,這是非常有説明。

最後,手稿類型系統支援結構打字。 在定義介面的重要類型,類型系統的手稿將承擔支援該介面,任何帶有這些方法和屬性的物件。 你不需要在每個類定義中聲明介面的支援。 匿名物件還可以支援使用此結構的打字功能的介面。

結合這些功能創建平滑路徑隨著您遷移您到手稿從 JavaScript 代碼庫。 進一步沿你時,你會從手稿靜態代碼分析的更多好處的遷移路徑。 您的最終目標應該是利用盡可能從手稿一樣安全。 一路走來,作為有效的手稿不會讓你現有 JavaScript 代碼的功能使用的手稿的類型批註。 這是一個幾乎無摩擦的過程。 你不要有任何理由不在您當前的 JavaScript 應用程式中使用手稿。

Bill Wagner  現在的暢銷的書,"有效 C#"(2004 年),作者是在其第二版,和"更有效 C#"(2008 年),兩個從艾迪生-衛斯理專業。他還寫了皮爾遜教育 informIT,"C# 非同步基本面 LiveLessons"和"C# 應用難點。"的視頻他積極的博客在 thebillwagner.com 可以達成和 bill.w.wagner@outlook.com

感謝以下 Microsoft 技術專家對本文的審閱:喬納森 ·Turner
喬納森 ·Turner是語言的在 Microsoft 手稿團隊的專案經理和設計師手稿之一。在加入微軟之前, 他在鏗鏘/LLVM 和程式設計語言的教堂工作。