這一章主要總結(jié)TypeScript的用法和項(xiàng)目常用配置
編譯上下文
用來(lái)給文件分組虎眨,告訴 TypeScript 哪些文件是有效的傅瞻,哪些是無(wú)效的亲澡。定義這種邏輯分組沥割,一個(gè)比較好的方式是使用 tsconfig.json
文件耗啦。
常用配置一覽
{
"compilerOptions": {
/* 基本選項(xiàng) */
"target": "es5", // 指定 ECMAScript 目標(biāo)版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模塊: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在編譯中的庫(kù)文件
"allowJs": true, // 允許編譯 javascript 文件
"checkJs": true, // 報(bào)告 javascript 文件中的錯(cuò)誤
"jsx": "preserve", // 指定 jsx 代碼的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相應(yīng)的 '.d.ts' 文件
"sourceMap": true, // 生成相應(yīng)的 '.map' 文件
"outFile": "./", // 將輸出文件合并為一個(gè)文件
"outDir": "./", // 指定輸出目錄
"rootDir": "./", // 用來(lái)控制輸出目錄結(jié)構(gòu) --outDir.
"removeComments": true, // 刪除編譯后的所有的注釋
"noEmit": true, // 不生成輸出文件
"importHelpers": true, // 從 tslib 導(dǎo)入輔助工具函數(shù)
"isolatedModules": true, // 將每個(gè)文件做為單獨(dú)的模塊 (與 'ts.transpileModule' 類似).
/* 嚴(yán)格的類型檢查選項(xiàng) */
"strict": true, // 啟用所有嚴(yán)格類型檢查選項(xiàng)
"noImplicitAny": true, // 在表達(dá)式和聲明上有隱含的 any類型時(shí)報(bào)錯(cuò)
"strictNullChecks": true, // 啟用嚴(yán)格的 null 檢查
"noImplicitThis": true, // 當(dāng) this 表達(dá)式值為 any 類型的時(shí)候,生成一個(gè)錯(cuò)誤
"alwaysStrict": true, // 以嚴(yán)格模式檢查每個(gè)模塊机杜,并在每個(gè)文件里加入 'use strict'
/* 額外的檢查 */
"noUnusedLocals": true, // 有未使用的變量時(shí)帜讲,拋出錯(cuò)誤
"noUnusedParameters": true, // 有未使用的參數(shù)時(shí),拋出錯(cuò)誤
"noImplicitReturns": true, // 并不是所有函數(shù)里的代碼都有返回值時(shí)椒拗,拋出錯(cuò)誤
"noFallthroughCasesInSwitch": true, // 報(bào)告 switch 語(yǔ)句的 fallthrough 錯(cuò)誤似将。(即,不允許 switch 的 case 語(yǔ)句貫穿)
/* 模塊解析選項(xiàng) */
"moduleResolution": "node", // 選擇模塊解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用于解析非相對(duì)模塊名稱的基目錄
"paths": {}, // 模塊名到基于 baseUrl 的路徑映射的列表
"rootDirs": [], // 根文件夾列表蚀苛,其組合內(nèi)容表示項(xiàng)目運(yùn)行時(shí)的結(jié)構(gòu)內(nèi)容
"typeRoots": [], // 包含類型聲明的文件列表
"types": [], // 需要包含的類型聲明文件名列表
"allowSyntheticDefaultImports": true, // 允許從沒(méi)有設(shè)置默認(rèn)導(dǎo)出的模塊中默認(rèn)導(dǎo)入在验。
/* Source Map Options */
"sourceRoot": "./", // 指定調(diào)試器應(yīng)該找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定調(diào)試器應(yīng)該找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成單個(gè) soucemaps 文件,而不是將 sourcemaps 生成不同的文件
"inlineSources": true, // 將代碼與 sourcemaps 生成到一個(gè)文件中,要求同時(shí)設(shè)置了 --inlineSourceMap 或 --sourceMap 屬性
/* 其他選項(xiàng) */
"experimentalDecorators": true, // 啟用裝飾器
"emitDecoratorMetadata": true // 為裝飾器提供元數(shù)據(jù)的支持
}
}
TypeScript 編譯
好的 IDE 支持對(duì) TypeScript 的即時(shí)編譯既峡。但是署恍,如果你想在使用 tsconfig.json
時(shí)從命令行手動(dòng)運(yùn)行 TypeScript 編譯器,你可以通過(guò)以下方式:
- 運(yùn)行 tsc侦厚,它會(huì)在當(dāng)前目錄或者是父級(jí)目錄尋找
tsconfig.json
文件。 - 運(yùn)行
tsc -p ./path-to-project-directory
拙徽。當(dāng)然刨沦,這個(gè)路徑可以是絕對(duì)路徑,也可以是相對(duì)于當(dāng)前目錄的相對(duì)路徑膘怕。
你甚至可以使用 tsc -w
來(lái)啟用 TypeScript 編譯器的觀測(cè)模式想诅,在檢測(cè)到文件改動(dòng)之后,它將重新編譯。
聲明空間
在 TypeScript 里存在兩種聲明空間:類型聲明空間與變量聲明空間来破。我將會(huì)在下文中和大家討論這兩個(gè)概念篮灼。
類型聲明空間
類型聲明空間包含用來(lái)當(dāng)做類型注解的內(nèi)容,例如以下的一些類型聲明:
class Foo {};
interface Bar {};
type Bas = {};
你可以將 Foo
, Bar
, Bas
做為類型注解使用徘禁,例如:
let foo: Foo;
let bar: Bar;
let bas: Bas;
注意诅诱,盡管你定義了 interface Bar
,你并不能夠?qū)⑺鰹橐粋€(gè)變量使用送朱,因?yàn)樗鼪](méi)有定義在變量聲明空間中:
interface Bar {}
const bar = Bar; // Error: "cannot find name 'Bar'"
提示 cannot find name 'Bar'
的原因是名稱 Bar
并未定義在變量聲明空間娘荡。這將帶領(lǐng)我們進(jìn)入下一個(gè)主題 "變量聲明空間"。
變量聲明空間
變量聲明空間包含可用作變量的內(nèi)容驶沼,在上文中 Class Foo
提供了一個(gè)類型 Foo
到類型聲明空間炮沐,此外它同樣提供了一個(gè)變量 Foo
到變量聲明空間,如下所示:
class Foo {}
const someVar = Foo;
const someOtherVar = 123;
這很棒回怜,尤其是當(dāng)你想把一個(gè)類來(lái)當(dāng)做變量傳遞時(shí)大年。
WARNING
我們并不能使用一些像
interface
定義的內(nèi)容,來(lái)當(dāng)做變量使用鹉戚。
與此相似鲜戒,一些像你用 var
聲明的變量,也僅能在變量聲明空間使用抹凳,不能用作類型注解遏餐。
const foo = 123;
let bar: foo; // ERROR: "cannot find name 'foo'"
提示 cannot find name
的原因是,名稱 foo
沒(méi)有定義在類型聲明空間里赢底。
模塊
全局模塊
默認(rèn)情況下失都,當(dāng)你開(kāi)始在一個(gè)新的 TypeScript 文件中寫(xiě)下代碼時(shí),它處于全局命名空間中幸冻。如在 foo.ts
里的以下代碼:
const foo = 123;
如果你在相同的項(xiàng)目里創(chuàng)建了一個(gè)新的文件 bar.ts
粹庞,TypeScript 類型系統(tǒng)將會(huì)允許你使用變量 foo
,就好像它在全局可用一樣:
const bar = foo; // allowed
毋庸置疑洽损,使用全局變量空間是危險(xiǎn)的庞溜,因?yàn)樗鼤?huì)與文件內(nèi)的代碼命名沖突。我們推薦使用下文中將要提到的文件模塊碑定。
文件模塊
它也被稱為外部模塊流码。如果在你的 TypeScript 文件的根級(jí)別位置含有 import
或者 export
,它會(huì)在這個(gè)文件中創(chuàng)建一個(gè)本地的作用域延刘。因此漫试,我們需要把上文 foo.ts
改成如下方式(注意 export
用法):
export const foo = 123;
在全局命名空間里,我們不再有 foo
碘赖,這可以通過(guò)創(chuàng)建一個(gè)新文件 bar.ts
來(lái)證明:
const bar = foo; // ERROR: "cannot find name 'foo'"
如果你想在 bar.ts
里使用來(lái)自 foo.ts
的內(nèi)容驾荣,你必須顯式導(dǎo)入它外构,更新 bar.ts
如下所示:
import { foo } from './foo';
const bar = foo; // allow
在 bar.ts
文件里使用 import
,不但允許你使用從其他文件導(dǎo)入的內(nèi)容播掷,而且它會(huì)將此文件 bar.ts
標(biāo)記為一個(gè)模塊审编,文件內(nèi)定義的聲明也不會(huì)污染全局命名空間。
文件模塊詳情
文件模塊擁有強(qiáng)大的能力和可用性歧匈。在這里割笙,我們來(lái)討論它的能力以及一些用法。
澄清:commonjs, amd, es modules, others
首先眯亦,我們需要澄清這些模塊系統(tǒng)的不一致性。我將會(huì)提供給你我當(dāng)前的建議般码,以及消除一些顧慮妻率。
你可以根據(jù)不同的 module
選項(xiàng)來(lái)把 TypeScript 編譯成不同的 JavaScript 模塊類型,這有一些你可以忽略的:
- AMD:不要使用它板祝,它僅能在瀏覽器工作宫静;
- SystemJS:這是一個(gè)好的實(shí)驗(yàn),已經(jīng)被 ES 模塊替代券时;
- ES 模塊:它并沒(méi)有準(zhǔn)備好孤里。
使用 module: commonjs
選項(xiàng)來(lái)替代這些模式,這會(huì)是一個(gè)好的主意橘洞。
怎么書(shū)寫(xiě) TypeScript 模塊捌袜,這也是一件讓人困惑的事。在今天我們應(yīng)該這么做:
-
import foo = require('foo')
例如:import/require
使用 ES 模塊語(yǔ)法炸枣。
這很酷虏等,接下來(lái),讓我們看看 ES 模塊語(yǔ)法适肠。
TIP
使用 module: commonjs
選項(xiàng)以及使用 ES 模塊語(yǔ)法導(dǎo)入導(dǎo)出其他模塊霍衫。
ES 模塊語(yǔ)法
- 使用
export
關(guān)鍵字導(dǎo)出一個(gè)變量(或者類型):
// foo.ts
export const someVar = 123;
export type someType = { foo: string;
};
-
export
的寫(xiě)法除了上面這樣,還有另外一種:
// foo.ts
const someVar = 123;
type someType = { type: string;
};
export { someVar, someType };
- 你也可以重命名變量導(dǎo)出:
// foo.ts
const someVar = 123;
export { someVar as aDifferentName };
- 使用
import
關(guān)鍵字導(dǎo)入一個(gè)變量或者是一個(gè)類型:
// bar.ts
import { someVar, someType } from './foo';
- 重命名導(dǎo)入變量或者類型:
// bar.ts
import { someVar as aDifferentName } from './foo';
- 除了指定加載某個(gè)輸出值侯养,還可以使用整體加載敦跌,即用星號(hào)(*)指定一個(gè)對(duì)象,所有輸出值都加載在這個(gè)對(duì)象上面:
// bar.ts
import * as foo from './foo';
// 你可以使用 `foo.someVar` 和 `foo.someType` 以及其他任何從 `foo` 導(dǎo)出的變量或者類型
- 僅導(dǎo)入模塊:
import 'core-js'; // 一個(gè)普通的 polyfill 庫(kù)
- 從其他模塊導(dǎo)入后整體導(dǎo)出:
export * from './foo';
- 從其他模塊導(dǎo)入后逛揩,部分導(dǎo)出:
export { someVar } from './foo';
- 通過(guò)重命名柠傍,部分導(dǎo)出從另一個(gè)模塊導(dǎo)入的項(xiàng)目:
export { someVar as aDifferentName } from './foo';
默認(rèn)導(dǎo)入/導(dǎo)出
我并不喜歡用默認(rèn)導(dǎo)出,雖然有默認(rèn)導(dǎo)出的語(yǔ)法:
- 使用
export default
- 在一個(gè)變量之前(不需要使用
let/const/var
)息尺; - 在一個(gè)函數(shù)之前携兵;
- 在一個(gè)類之前。
- 在一個(gè)變量之前(不需要使用
// some var
export default (someVar = 123);
// some function
export default function someFunction() {}
// some class
export default class someClass {}
- 導(dǎo)入使用
import someName from 'someModule'
語(yǔ)法(你可以根據(jù)需要為導(dǎo)入命名):
import someLocalNameForThisFile from './foo';
模塊路徑
TIP
假設(shè)你使用 moduleResolution: node
選項(xiàng)搂誉。這個(gè)選項(xiàng)應(yīng)該在你 TypeScript 配置文件里徐紧。如果你使用了 module: commonjs
選項(xiàng), moduleResolution: node
將會(huì)默認(rèn)開(kāi)啟。
這里存在兩種不同截然不同的模塊并级,它們是由導(dǎo)入語(yǔ)句中的不同的路徑寫(xiě)法所引起的(例如:import foo from 'THIS IS THE PATH SECTION'
)拂檩。
- 相對(duì)模塊路徑(路徑以
.
開(kāi)頭,例如:./someFile
或者../../someFolder/someFile
等)嘲碧; - 其他動(dòng)態(tài)查找模塊(如:
core-js
稻励,typestyle
,react
或者甚至是react/core
等)愈涩。
它們的主要區(qū)別來(lái)自于系統(tǒng)如何解析模塊望抽。
TIP
我將會(huì)使用一個(gè)概念性術(shù)語(yǔ),place
-- 將在提及查找模式后解釋它履婉。
相對(duì)模塊路徑
這很簡(jiǎn)單煤篙,僅僅是按照相對(duì)路徑:
- 如果文件
bar.ts
中含有import * as foo from './foo'
,foo
文件所存在的地方必須是相同文件夾下毁腿; - 如果文件
bar.ts
中含有import * as foo from '../foo'
辑奈,foo
文件所存在的地方必須是上一級(jí)目錄; - 如果文件
bar.ts
中含有import * as foo from '../someFolder/foo'
已烤,foo
文件所在的文件夾someFolder
必須與bar.ts
所在文件夾在相同的目錄下鸠窗。
或者,你還可以想想其他相對(duì)路徑導(dǎo)入的情景胯究。??
動(dòng)態(tài)查找
當(dāng)導(dǎo)入路徑不是相對(duì)路徑時(shí)稍计,模塊解析將會(huì)模仿 Node 模塊解析策略,以下我將給出一個(gè)簡(jiǎn)單例子:
- 當(dāng)你使用
import * as foo from 'foo'
裕循,將會(huì)按如下順序查找模塊:./node_modules/foo
../node_modules/foo
../../node_modules/foo
- 直到系統(tǒng)的根目錄
- 當(dāng)你使用
import * as foo from 'something/foo'
丙猬,將會(huì)按照如下順序查找內(nèi)容./node_modules/something/foo
../node_modules/something/foo
../../node_modules/something/foo
- 直到系統(tǒng)的根目錄
什么是 place
當(dāng)我提及被檢查的 place
時(shí),我想表達(dá)的是在這個(gè) place
费韭,TypeScript 將會(huì)檢查以下內(nèi)容(例如一個(gè) foo
的位置):
- 如果這個(gè)
place
表示一個(gè)文件茧球,如:foo.ts
,歡呼星持! - 否則抢埋,如果這個(gè)
place
是一個(gè)文件夾,并且存在一個(gè)文件foo/index.ts
督暂,歡呼揪垄! - 否則,如果這個(gè)
place
是一個(gè)文件夾逻翁,并且存在一個(gè)foo/package.json
文件饥努,在該文件中指定types
的文件存在,那么就歡呼八回! - 否則酷愧,如果這個(gè)
place
是一個(gè)文件夾驾诈,并且存在一個(gè)package.json
文件,在該文件中指定main
的文件存在溶浴,那么就歡呼乍迄!
從文件類型上來(lái)說(shuō),我實(shí)際上是指 .ts
士败, .d.ts
或者 .js
就是這樣闯两,現(xiàn)在你已經(jīng)是一個(gè)模塊查找專家(這并不是一個(gè)小小的成功)。
重寫(xiě)類型的動(dòng)態(tài)查找
在你的項(xiàng)目里谅将,你可以通過(guò) declare module 'somePath'
來(lái)聲明一個(gè)全局模塊的方式漾狼,用來(lái)解決查找模塊路徑的問(wèn)題:
// globals.d.ts
declare module 'foo' { // some variable declarations export var bar: number;
}
接著:
// anyOtherTsFileInYourProject.ts
import * as foo from 'foo';
// TypeScript 將假設(shè)(在沒(méi)有做其他查找的情況下)
// foo 是 { bar: number }
import/require
僅僅是導(dǎo)入類型
以下導(dǎo)入語(yǔ)法:
import foo = require('foo');
它實(shí)際上只做了兩件事:
- 導(dǎo)入 foo 模塊的所有類型信息;
- 確定 foo 模塊運(yùn)行時(shí)的依賴關(guān)系饥臂。
你可以選擇僅加載類型信息邦投,而沒(méi)有運(yùn)行時(shí)的依賴關(guān)系。在繼續(xù)之前擅笔,你可能需要重新閱讀本書(shū)的 聲明空間部分 部分。
如果你沒(méi)有把導(dǎo)入的名稱當(dāng)做變量聲明空間來(lái)用屯援,在編譯成 JavaScript 時(shí)猛们,導(dǎo)入的模塊將會(huì)被完全移除。這有一些較好的例子狞洋,當(dāng)你了解了它們之后弯淘,我們將會(huì)給出一些使用例子。
例子 1
import foo = require('foo');
將會(huì)編譯成 JavaScript:
這是正確的吉懊,一個(gè)沒(méi)有被使用的空文件庐橙。
例子 2
import foo = require('foo');
var bar: foo;
將會(huì)被編譯成:
let bar;
這是因?yàn)?foo (或者其他任何屬性如:foo.bas
)沒(méi)有被當(dāng)做一個(gè)變量使用。
例子 3
import foo = require('foo');
const bar = foo;
將會(huì)被編譯成(假設(shè)是 commonjs):
const foo = require('foo');
const bar = foo;
這是因?yàn)?foo
被當(dāng)做變量使用了借嗽。
使用例子:懶加載
類型推斷需要提前完成态鳖,這意味著,如果你想在 bar
文件里恶导,使用從其他文件 foo
導(dǎo)出的類型浆竭,你將不得不這么做:
import foo = require('foo');
let bar: foo.SomeType;
然而,在某些情景下惨寿,你只想在需要時(shí)加載模塊 foo
邦泄,此時(shí)你需要僅在類型注解中使用導(dǎo)入的模塊名稱,而不是在變量中使用裂垦。在編譯成 JavaScript 式顺囊,這些將會(huì)被移除。接著蕉拢,你可以手動(dòng)導(dǎo)入你需要的模塊特碳。
做為一個(gè)例子诚亚,考慮以下基于 commonjs
的代碼,我們僅在一個(gè)函數(shù)內(nèi)導(dǎo)入 foo
模塊:
import foo = require('foo');
export function loadFoo() { // 這是懶加載 foo测萎,原始的加載僅僅用來(lái)做類型注解 const _foo: typeof foo = require('foo'); // 現(xiàn)在亡电,你可以使用 `_foo` 替代 `foo` 來(lái)做為一個(gè)變量使用
}
一個(gè)同樣簡(jiǎn)單的 amd
模塊(使用 requirejs):
import foo = require('foo');
export function loadFoo() { // 這是懶加載 foo,原始的加載僅僅用來(lái)做類型注解 require(['foo'], (_foo: typeof foo) => { // 現(xiàn)在硅瞧,你可以使用 `_foo` 替代 `foo` 來(lái)做為一個(gè)變量使用 });
}
這些通常在以下情景使用:
- 在 web app 里份乒, 當(dāng)你在特定路由上加載 JavaScript 時(shí);
- 在 node 應(yīng)用里腕唧,當(dāng)你只想加載特定模塊或辖,用來(lái)加快啟動(dòng)速度時(shí)。
使用例子:打破循環(huán)依賴
類似于懶加載的使用用例枣接,某些模塊加載器(commonjs/node 和 amd/requirejs)不能很好的處理循環(huán)依賴颂暇。在這種情況下,一方面我們使用延遲加載代碼但惶,并在另一方面預(yù)先加載模塊時(shí)很實(shí)用的耳鸯。
使用例子:確保導(dǎo)入
當(dāng)你加載一個(gè)模塊,只是想引入其附加的作用(如:模塊可能會(huì)注冊(cè)一些像 CodeMirror addons)時(shí)膀曾,然而县爬,如果你僅僅是 import/require
(導(dǎo)入)一些并沒(méi)有與你的模塊或者模塊加載器有任何依賴的 JavaScript 代碼,(如:webpack)添谊,經(jīng)過(guò) TypeScript 編譯后财喳,這些將會(huì)被完全忽視。在這種情況下斩狱,你可以使用一個(gè) ensureImport
變量耳高,來(lái)確保編譯的 JavaScript 依賴與模塊。如:
import foo = require('./foo');
import bar = require('./bar');
import bas = require('./bas');
const ensureImport: any = foo || bar || bas;
globals.d.ts
在上文中所踊,當(dāng)我們討論文件模塊時(shí)泌枪,比較了全局變量與文件模塊,并且我們推薦使用基于文件的模塊秕岛,而不是選擇污染全局命名空間工闺。
然而,如果你的團(tuán)隊(duì)里有 TypeScript 初學(xué)者瓣蛀,你可以提供他們一個(gè) globals.d.ts
文件陆蟆,用來(lái)將一些接口或者類型放入全局命名空間里,這些定義的接口和類型能在你的所有 TypeScript 代碼里使用惋增。
TIP
對(duì)于任何需要編譯成 JavaScript 代碼叠殷,我們強(qiáng)烈建議你放入文件模塊里。
-
globals.d.ts
是一種擴(kuò)充lib.d.ts
很好的方式诈皿,如果你需要林束。 - 當(dāng)你從
TS
遷移到JS
時(shí)像棘,定義declare module "some-library-you-dont-care-to-get-defs-for"
能讓你快速開(kāi)始。
轉(zhuǎn)自 https://jkchao.github.io/typescript-book-chinese/project/modules.html