Angular2管理外部類型定義和處理「Duplicate identifier」 TypeScript錯誤

在學習angular2的時候,根據angular2中文網提供的quickstart做爲種子項目。在安裝完全部包並運行項目的時候,出現了滿屏的編譯錯誤:html

error TS2300: Duplicate identifier

在這裏就聊聊這個錯誤的緣由和如何修復它。爲了能更好的明白這個問題,咱們先討論TypeScript和Javascript,以及外部類型定義和@types之間的不一樣。node

介紹

在深刻以前,先說一下TypeScript的外部類型定義。
若是你比較熟悉TypeScript和JavaScript之間的區別,能夠跳過這一部分。jquery

TypeScript是靜態類型語言,是JavaScript的超集。靜態類型意思是咱們所寫的程序要通過一個編譯的階段,在這個階段,編譯器執行類型檢查,在必定程度上覈實正確性。例如:git

// person-human.ts

class Person {
  talk() {
    // ...
  }
}

class Superhero extends Person {
  fly() {
    // ...
  }
}

let bar = new Person();

bar.fly();

若是咱們編譯上面的代碼,TypeScript編譯器會報錯:es6

$ tsc person-human.ts
Property 'fly' does not exists on type 'Person'.

若是咱們運行相應的JavaScript文件,獲得錯誤:github

$ node person-human.js
Uncaught TypeError: bar.fly is not a function

雖然上面的兩種腳本都報錯,可是tsc是在編譯時間報的錯,而node是在運行時間報的錯。typescript

編譯時間的錯誤是容易被開發者處理的,由於錯誤是基於開發者的靜態代碼分析的。意味着,在用戶使用代碼前,編譯器可以通知開發者可能的錯誤。npm

另外一方面,咱們使用JavaScript動態類型,咱們不可能找出全部咱們犯的錯誤。爲動態類型語言的代碼進行靜態代碼分析是很難的(幾乎是不可能的),所以,代碼發佈到生產環境後,仍然沒有找到可能存在的問題也是至關有可能的。json

外部類型定義

既然TypeScript是JavaScript的超集,咱們想一塊兒使用TypeScript代碼和JavaScript代碼。例如,咱們想在Angular2(使用TypeScript寫的框架)中使用JQuery。數組

方案就是:TypeScript編譯成JavaScript代碼,編譯以後就能跟JavaScript代碼進行通訊,很不錯吧。可是如何在TypeScript代碼中使用JQuery?看一下下面的例子:

// jquery-demo.ts
let a = $('.foo');

咱們調用了$函數選擇器,上面的代碼會產生一個下面的編譯錯誤:

$ tsc jquery-demo.ts
Cannot find name '$'.

這是由於TypeScript比JavaScript嚴格,它不能假設,在咱們頁面裏可能包含了一個JQuery的引用,並依賴此引用。TypeScript須要有一個$的申明

這就是外部類型定義出現的緣由,爲了聲明,咱們有一個叫作$的全局函數,咱們能夠這樣作:

// jquery-definition.ts
declare var $: Function;

let a = $('.foo');

咱們再運行:

$ tsc jquery-definition.ts

# Will output the file jquery-definition.js

注意:咱們僅僅是聲明瞭一個$函數,而沒有給$函數定義。若是咱們運行jquery-definition.js,咱們仍然會獲得一個運行錯誤,由於$沒有定義(undefined)。

爲何要用外部類型定義

外部類型定義的主要目的是容許文本編輯器和IDE可以對JavaScript寫的包和框架進行靜態分析。這是頗有益處的,由於咱們可以編譯錯誤以及開發時可以智能感知。可以減小在運行時,因爲屬性名拼寫錯誤或傳遞錯誤對象給函數而致使的錯誤。

管理外部類型定義

比較流行用JavaScript寫的包均可以使用外部類型定義,例如JQuery、AngularJS 1.x、React等等。

幾年前,DefinitelyTyped建立了一個叫做tsd的CLI(Command-Line Interface)管理器

後來這個工具被棄用而且被更先進的typings替換。Typings容許咱們下載,咱們正在使用的包的外部類型定義

做爲TypeScript 2發佈的一部分,微軟宣佈了一種方法,用npm註冊去管理TypeScript外部的類型定義

在tsconfig.json的compilerOptions中,包含一個lib屬性。它包含了一個library文件集合,以及library相對應的類型定義。意味着若是你的編譯目標是ES5併爲瀏覽器building,那麼應該包含es5和dom,若是你要用ES6的特性,那麼你應該包含es6,等等。更多關於lib屬性參考這裏
安裝JQuery的外部類型定義

$ npm i @types/jquery --save-dev

注意:使用--save-dev比--save更合適,由於你不會但願你的npm package的用戶,去安裝你的第三方依賴的類型定義

使用外部類型定義

如何使用提供的類型定義呢?

在咱們安裝JQuery的外部類型定義的目錄下,建立下面的文件:

// jquery-demo.ts

/// <reference path="./node_modules/@types/jquery/index.d.ts"/>
let height = $('.foo').height();

雖然能夠運行,可是至關不靈活。若是咱們改變了定義文件的路徑或是其餘使用了定義的文件路徑被改變,咱們就須要改變path屬性的值。tsconfig.json給咱們提供了一個靈活的特性:

tsconfig.json

tsconfig.json是TypeScript定義的配置文件,在tsconfig.json中,你能配置TypeScript的編譯器。這裏有個列子:

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "es6"],
    "typeRoots": [
      "./node_modules/@types"
    ]
  },
  "exclude": [
    "node_modules",
    "dist"
  ],
  "compileOnSave": false
}

爲了告訴tsc到哪尋找安裝的外部類型定義,你可使用typeRoots數組,是compilerOptions的屬性。

在配置文件中,使用es5做爲目標語言,排除node_modules和dist目錄。

爲了有ES6 APIs的類型定,須要安裝@types/core-js:

$ npm i @types/core-js

最後咱們的目錄結構爲:

.
├── jquery-demo.js
├── jquery-demo.ts
├── tsconfig.json
└── node_modules
    └── @types
        ├── jquery
        │   ├── package.json
        │   │── types-metadata.json
        │   └── index.d.ts
        └── core-js
            ├── package.json
            │── types-metadata.json
            └── index.d.ts

因爲tsconfig.json,咱們能用下面命令編譯jquery-demo.ts文件:

$ tsc

結果,獲得不少錯誤

./../.npm-packages/lib/node_modules/typescript/lib/lib.es2015.core.d.ts(17,14): error TS2300: Duplicate identifier 'PropertyKey'.
node_modules/@types/core-js/index.d.ts(21,14): error TS2300: Duplicate identifier 'PropertyKey'.
node_modules/@types/core-js/index.d.ts(85,5): error TS2687: All declarations of 'name' must have identical modifiers.
node_modules/@types/core-js/index.d.ts(145,5): error TS2403: Subsequent variable declarations must have the same type.  Variable '[Symbol.unscopables]' must be of type '{ copyWithin: boolean; entries: boolean; fill: boolean; find: boolean; findIndex: boolean; keys: ...', but here has type 'any'.
node_modules/@types/core-js/index.d.ts(262,5): error TS2687: All declarations of 'flags' must have identical modifiers.
...

這由相同類型定義的多版本致使的。在tsconfig.json中,lib屬性包含了es6。同時在node_modules/@types中也有core-js模塊。爲了修復這個問題,兩個選項:

  • 在compilerOptions的lib屬性中,改es6爲es5.這種方法TypeScript不能包含ES6類型定義

  • 從node_modules中刪除core-js模塊。這種方式TypeScript僅能使用內部的ES6類型定義

由於使用TypeScript自帶的ES6類型定義比第三方的更靠譜,那就刪除node_modules/@types/core-js吧

$ rm -rf node_modules/@types/core-js

若是咱們再運行tsc, 會獲得編譯過的jquery-demo.js文件

即便咱們從jquery-demo.ts刪除<reference/>標籤,仍然是運行正常的。tsc的行爲是:「Take all the files and all the type definitions from the current directory and all of its subdirectories, except the ones declared in the exclude array」,意味着咱們也能使用tsconfig.json的exclude屬性管理外部類型定義

tsconfig.json還有files屬性,若是設置了它,tsc將值考慮files中包含的文件。

沒有命名空間的類型定義

你可能疑惑的爲何compilerOptions的lib和core-js有相同的類型定義?TypeScript外部類型定義是不能用命名空間。爲何不呢?若是在項目中使用JQuery,咱們不能包含兩個不一樣的外部類型定義集合,由於只有一個單獨的JQuery全局變量

參考連接:
http://blog.mgechev.com/2016/...
http://stackoverflow.com/ques...

相關文章
相關標籤/搜索