配置項 allowJs
是否編譯 .js
文件. 如果你的項目中有自定義的 .js
文件, 并且在 .ts
文件內有引入該 .js
文件, 那么必須設置 allowJs: true
, 否則 tsc
編譯后不會將該 .js
文件編譯到 dist
("outDir": "./dist"
) 目錄內, 運行時會造成找不模塊錯誤.
// allowJs:fasle
├─tsconfig.json
├─src
| ├─index.ts // 引入了 ./js/index
| ├─js
| | ├─index.d.ts
| | └index.js
├─dist
| └index.js // 為編譯js文件
// allowJs: true
├─tsconfig.json
├─src
| ├─index.ts // 引入了 ./js/index
| ├─js
| | ├─index.d.ts
| | └index.js
├─dist
| ├─index.js
| ├─js // 編譯了js文件
| | └index.js
其中:
// src/index.ts
import { testJs } from './js/index'
console.log(testJs("bgg"))
// src/js/index.js
export function testJs(name) {
name = 'hello' + name
return name
}
allowJs
與 .d.ts
(src/js文件夾內
) 的不同組合情況
-
allowJS: fasle
(默認值) 并且無.d.ts
文件時,在vscode
編輯器中, 會直接用紅色波浪線報錯, "無法找到模塊./js/index
的聲明文件". -
allowJS: false
并且存在.d.ts
文件時: 編輯器內不再提示報錯, 但因為allowJs: false
, 在運行編譯后的文件時仍然報錯, 因為src/js/index.js
文件根本不會編譯到dist
文件夾內. -
allowJs: true
并且無.d.ts
文件時: 編輯器不報錯,編譯后可以正常運行, 但.ts
引入.js
文件的方法/函數沒有類型提醒,參數是默認的any
類型. 這種情況下可以使用jsDoc
來讓編輯帶有類型提示功能.例如:
// src/js/index.js
/**
*測試js文件編譯
*
* @export
* @param {string} name 名字
* @returns 打招呼
*/
export function testJs(name) {
name = 'hello' + name
return name
}
此時, 當 testJs(123)
傳入數字類型時, 編輯器會用紅色下劃線標識傳入數據類型錯誤, 但是 tsc
命令仍然可以編譯通過, 因此 jsDoc
方式只是讓編輯器報錯,并不能阻止編譯過程.
-
allowJS: true
并且存在.d.ts
文件時, 編輯器不報錯,編譯后可以正常運行, 類型提示使用.d.ts
內定義的類型(不再使用js文件的jsDoc)
// src/js/index.d.ts
/**
*測試js文件的編譯
*
* @export
* @param {string} name 名字
* @returns {string} 返回打招呼
*/
export function testJs(name: string): string
此時, 當 testJs(123)
傳入數字類型時, 編輯器會用紅色下劃線標識傳入數據類型錯誤, 同時 tsc
命令編譯時不通過,編譯報錯如下:
E:\demo_ts>tsc
src/index.ts:3:20 - error TS2345: Argument of type '123' is not assignable to parameter of type 'string'.
3 console.log(testJs(123))
~~~
Found 1 error.
這能幫我們更好的理解 .d.ts
文件的作用.
.d.ts
文件的理解
通過上文"配置項 allowJs
"章節中示例代碼, 讓我們簡單了解到了 .d.ts
的作用, .d.ts
文件并不具有"執行"功能, 只是真實業務代碼(js
文件)的聲明文件. 它解決了源js
文件,在 typescript
編譯過程中因為類型檢測失敗報錯的問題(注意:只是編譯階段才有用).
在模塊化編程的項目中, 我們可以為每個模塊都定義一個 .d.ts
文件(.d.ts
中使用頂級的 export
聲明), 例如上面的例子. 但我們其實可以把所有的模塊聲明在一個 .d.ts
文件中, 官網文檔中其實也是建議這種形式的. 見模塊/外部模塊章節:
我們可以使用頂級的
export
聲明來為每個模塊都定義一個.d.ts
文件,但最好還是寫在一個大的.d.ts
文件里。 我們使用與構造一個外部命名空間相似的方法,但是這里使用module
關鍵字并且把名字用引號括起來,方便之后import
。
這里我們可以知道, .d.ts
并不是必須要依賴源模塊文件的, 也不是隨模塊文件的加載而加載的. 我們平時把模塊文件和 .d.ts
文件一一對應只是為了項目的方便管理, 并不是 typescript
的要求.
通過 declare module
合并聲明的語法見下文 "declare
的使用"章節.
關于 .d.ts
放的位置
一般來說,ts
會解析項目中所有的 *.ts
文件,當然也包含以 .d.ts
結尾的文件。所以當我們將 jQuery.d.ts
(以jQuery
為例, 通過script
標簽全局引入) 放到項目中時,其他所有 *.ts
文件就都可以獲得 jQuery
的類型定義了。
// src/jQuery.d.ts
declare var jQuery: (selector: string) => any;
// src/index.ts
jQuery('#foo');
假如仍然無法解析,那么可以檢查下 tsconfig.json
中的 files、include
和 exclude
配置.
實時上, 我們甚至可以不書寫 .d.ts
文件, 把類型聲明直接寫到 ts
文件中, 以上文 jQuery
全局引入為例:
// src/index.ts
declare var jQuery: (selector: string) => any;
jQuery('#foo');
or
$('#foo')
核心概念 (摘自中文網)
類型
類型通過以下方式引入:
- 類型別名聲明(type sn = number | string;)
- 接口聲明(interface I { x: number[]; })
- 類聲明(class C { })
- 枚舉聲明(enum E { A, B, C })
- 指向某個類型的import聲明
以上每種聲明形式都會創建一個新的類型名稱。
值
與類型相比,你可能已經理解了什么是值。 值是運行時名字,可以在表達式里引用。 比如 let x = 5
;創建一個名為x
的值。
同樣,以下方式能夠創建值:
- let,const,和var聲明
- 包含值的namespace或module聲明
- enum聲明
- class聲明
- 指向值的import聲明
- function聲明
命名空間
類型可以存在于命名空間里。 比如,有這樣的聲明 let x: A.B.C, 我們就認為 C類型來自A.B命名空間。
由上面類型/值的創建方式可知, 命名空間是屬于創建值的方式,而不是類型的創建方式
簡單的組合:一個名字,多種意義
一個給定的名字A,我們可以找出三種不同的意義:一個類型,一個值或一個命名空間。 要如何去解析這個名字要看它所在的上下文是怎樣的。 比如,在聲明 let m: A.A = A;, A首先被當做命名空間,然后做為類型名,最后是值。 這些意義最終可能會指向完全不同的聲明!
內置組合
眼尖的讀者可能會注意到,比如,class同時出現在類型和值列表里。 class C { }聲明創建了兩個東西: 類型C指向類的實例結構, 值C指向類構造函數。 枚舉聲明擁有相似的行為。
用戶組合
假設我們寫了模塊文件foo.d.ts:
export var SomeVar: { a: SomeType };
export interface SomeType {
count: number;
}
這樣使用它:
import * as foo from './foo';
let x: foo.SomeType = foo.SomeVar.a;
console.log(x.count);
這可以很好地工作,但是我們知道SomeType和SomeVar很相關 因此我們想讓他們有相同的名字。 我們可以使用組合通過相同的名字 Bar表示這兩種不同的對象(值和對象):
export var Bar: { a: Bar };
export interface Bar {
count: number;
}
這提供了解構使用的機會:
import { Bar } from './foo';
let x: Bar = Bar.a;
console.log(x.count);
再次地,這里我們使用Bar做為類型和值。 注意我們沒有聲明 Bar值為Bar類型 -- 它們是獨立的。
declare
的使用
在 .d.ts
文件中使用declare
來聲明變量的類型, 能用在全局命名空間(全局聲明)或者包聲明文件(聲明一個局部變量)中, 這個聲明僅僅用于編譯時的檢查,在編譯結果中會被刪除.
聲明全局變量
declare var foo: number;
declare const foo: number;
declare let foo: number;
聲明全局函數
declare function greet(greeting: string): void;
declare namespace
描述用點表示法訪問的類型或值(對象)
注意 namespace
內代碼的寫法和在全局變量下是一樣的, 也是寫 function
, let
declare namespace myLib {
function makeGreeting(s: string): string;
let numberOfGreetings: number;
}
// 代碼中使用
let result = myLib.makeGreeting("hello, world");
console.log("The computed greeting is:" + result);
let count = myLib.numberOfGreetings;
declare module
聲明模塊之一
在書寫模塊插件的 .d.ts
時, 聲明相同的模塊名(插件是為了增強這個模塊)
/*~ On this line, import the module which this module adds to */
import * as m from 'someModule';
/*~ You can also import other modules if needed */
import * as other from 'anotherModule';
/*~ Here, declare the same module as the one you imported above */
declare module 'someModule' {
/*~ Inside, add new function, classes, or variables. You can use
*~ unexported types from the original module if needed. */
export function theNewMethod(x: m.foo): other.bar;
/*~ You can also add new properties to existing interfaces from
*~ the original module by writing interface augmentations */
export interface SomeModuleOptions {
someModuleSetting?: string;
}
/*~ New types can also be declared and will appear as if they
*~ are in the original module */
export interface MyModulePluginOptions {
size: number;
}
}
declare module
聲明模塊之二
在前端工程中,import
很多非 js
資源,例如:css, html, 圖片,vue,
這種 ts
無法識別的資源時,就需要告訴ts
,怎么識別這些導入的資源的類型。
// 看看vue怎么處理的:shims-vue.d.ts
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}
// html
declare module '*.html';
// css
declare module '*.css';
declare module
聲明模塊之三
和上文"之二"有類似效果, 可以認為都是"模塊補充"
// 聲明合并效果
// vue的聲明在 vue/types/vue.d.ts
import Vue from 'vue'
declare module 'vue/types/vue' {
// 相當于Vue.$eventBus
interface Vue {
$eventBus: Vue;
}
// 相當于在Vue.prototype.$eventBus 即全局屬性
interface VueConstructor {
$eventBus: Vue;
}
}
// 聲明vue中額外的組件選項
// ComponentOptions 聲明于 types/options.d.ts 之中
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
myOption?: string
}
}
declare module
聲明模塊之四
用于外部模塊的統一聲明, 即把所有模塊的聲明寫到一個 .d.ts
文件中(理解見上文".d.ts文件的理解")
// node.d.ts
declare module "url" {
export interface Url {
protocol?: string;
hostname?: string;
pathname?: string;
}
export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
}
declare module "path" {
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export let sep: string;
}
外部模塊簡寫: 假如你不想在使用一個新模塊之前花時間去編寫聲明,你可以采用聲明的簡寫形式以便能夠快速使用它。
// declarations.d.ts
// 簡寫模塊里所有導出的類型將是any
declare module "hot-new-module";
//---------
// ts文件中引入模塊時
import x, {y} from "hot-new-module";
x(y);
模塊聲明通配符: 某些模塊加載器如 SystemJS
和 AMD
支持導入非 JavaScript
內容。 它們通常會使用一個前綴或后綴來表示特殊的加載語法。 模塊聲明通配符可以用來表示這些情況。
// xxx.d.ts
declare module "*!text" {
const content: string;
export default content;
}
// Some do it the other way around.
declare module "json!*" {
const value: any;
export default value;
}
// ---------
//現在你可以就導入匹配"*!text"或"json!*"的內容了。
import fileContent from "./xyz.txt!text";
import data from "json!http://example.com/data.json";
console.log(data, fileContent);
注意: 沒有 declare interface
的寫法, 需要聲明接口直接寫 interface
,或者在命名空間中 export interface
即可!
理解 namespace
命名空間: 作為全局命名空間的子空間存在. 在書寫 .d.ts
時:
- 可以通過
declare
聲明 - 書寫
namespacke
內部的代碼時和寫全局命名空間一樣.例如可以寫export
,var
等, 而不是因為命名空間后面有{}
就認為是對象(在非.d.ts
文件內可以認為是對象)
// module-class.d.ts 類模塊的聲明文件
export = MyClass;
/*~ Write your module's methods and properties in this class */
declare class MyClass {
constructor(someParam?: string);
someProperty: string[];
myMethod(opts: MyClass.MyClassMethodOptions): number;
}
/*~ If you want to expose types from your module as well, you can
*~ place them in this block.
*/
declare namespace MyClass {
export interface MyClassMethodOptions {
width?: number;
height?: number;
}
}