tsconfig常用配置全解
- 2022 年 7 月 19 日
- 筆記
- 前端, typescript, frontend
- include, exclude, files配置項
- extends配置
- compilerOptions下的配置
- compilerOptions.allowUnreachableCode
- compilerOptions.allowUnusedLabels
- compilerOptions.alwaysStrict
- compilerOptions.exactOptionalProperties
- compilerOptions.downlevelIteration
- compilerOptions.importHelpers
- compilerOptions.strict
- compilerOptions.strictBindCallApply
- compilerOptions.strictFunctionTypes
- compilerOptions.strictNullChecks
- compilerOptions.strictPropertyInitialization
- compilerOptions.noImplicitAny
- compilerOptions.noImplicitOverride
- compilerOptions.noImplicitReturns
- compilerOptions.noImplicitThis
- compilerOptions.noPropertyAccessFromIndexSignature
- compilerOptions.noUncheckedIndexedAccess
- compilerOptions.noUnusedLocals
- compilerOptions.noUnusedParameters
- compilerOptions.useUnknownInCatchVariables
- 小結
基於typescript的項目的根目錄下都會有一個文件(tsconfig.json), 這個文件主要用來控制typescript編譯器(tsc, typescript compiler)的一些行為, 比如指定哪些文件需要讓tsc來編譯, 哪些文件不想讓tsc進行編譯之類的.
angular項目的tsconfig.json文件
tsconfig.json
/* To learn more about this file see: //angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"module": "es2020",
"lib": [
"es2018",
"dom"
]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictTemplates": true
}
}
這其中angularCompilerOptions顧名思義是angular專用的, 不在本文討論範圍.
include, exclude, files配置項
include: 指定要編譯哪些文件, 比如只需要編譯<project-dir>/src目錄下的.ts源程式碼文件
{
"compilerOptions": {
...
},
include: ["./src/**/*", "./demo/**/*.tsx?"]
}
上面的include配置用到了兩個通配符: **/, *
**/表示匹配任何子路徑, 包括目錄分隔符/也會被它匹配, 所以用來這個通配符後, 目錄下有多少子目錄都會被匹配到
*表示匹配除了目錄分隔符(/)外的任何長度的字元串
?表示匹配一個除文件分隔符(/)外的任一字元
顯然./src/**/*即表示匹配src文件夾下的任何子文件夾的任何文件; 而./demo/**/*.tsx?即表示匹配demo目錄下任何子目錄下的任意以.ts或.tsx結尾的文件
include其實就是一個白名單, 在這個白名單里被匹配到的文件才會被tsc處理編譯
相對於include是作為白名單的配置, exclude選項就是一個黑名單了, 它的值和include一樣是一個路徑名字元串數組, 最常見的用處就是用來排除掉node_modules目錄下的文件
{
"compilerOptions": {
...
},
include: ["./src/**/*", "./demo/**/*.tsx?"],
exclude: ["node_modules/**/*"]
}
當然也可以用exclude排除掉include裡面包含到的文件
有些情況即使exclude了某些文件後, 編譯後的程式碼中可能仍然包含被
exclude了的內容, 比如通過import導入了被exclude了的node_modules文件夾, 此時tsc仍然會去處理被exclude了的文件, 這一點應該不難理解
files 配置的作用類似include, 也是一個白名單路徑數組, 不同在於它不能使用通配符, 而必須使用精確的文件路徑(可以是相對路徑), 比如如果項目只有一個入口文件, 那麼就可以使用在只用files配置這個文件的路徑, 然後其他的文件都通過index.ts來import
tsconfig.json
{
"compilerOptions": {
...
},
// include: ["./src/**/*", "./demo/**/*.tsx?"],
// exclude: ["node_modules/**/*"]
files: ['./src/index.ts']
}
extends配置
extends 用於在一個tsconfig.json文件中擴展其他tsconfig.json文件, 比如angular項目中有三個tsconfig配置文件: tsconfig.json, tsconfig.spec.json, tsconfig.app.json
tsconfig.json
/* To learn more about this file see: //angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"module": "es2020",
"lib": [
"es2018",
"dom"
]
},
...
}
tsconfig.app.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}
tsconfig.spec.json
/* To learn more about this file see: //angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
...
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}
從命名和文件內容上即可看出之所以這麼做是為了針對測試文件.spec.ts和普通.ts文件在使用不同的配置時又能共享他們相同部分的配置, 達到避免重複的目的
compilerOptions下的配置
compilerOptions.allowUnreachableCode
表示是否允許程式碼中出現永遠無法被執行到的程式碼, 可選值是undefined, false, true
{
"compilerOptions": {
"allowUnreachableCode": false
...
},
...
}
什麼是"永遠無法被執行到的程式碼"?
const foo = () => {
return 0;
console.log('aaa'); // 這一行程式碼就是永遠被執行到的程式碼
}配置為undefined時, 遇到這種情況會給出warning, 配置false則直接編譯時拋出錯誤無法成功編譯, 配置為true既沒有警告也沒有錯誤
compilerOptions.allowUnusedLabels
這個選項是針對標籤(label)語法的, 這個語法很罕見, 我也是看到了這個配置才知道有這個原來js還有Label語法, label語法有點像其他語言里的goto, 真是場景中幾乎不用
compilerOptions.allowUnusedLabels表示是否允許未使用到的標籤
可選項:
undefined: 這是默認值, 碰到未使用的標籤給出warning警告false: 碰到未使用的標籤拋出錯誤, 編譯失敗true: 碰到未使用的標籤編譯通過, 且不給出異常
function bar() {
console.log('loafing...');
loop: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i === 2) {
// break loop;
}
console.log(i, j, i + j);
}
}
}

compilerOptions.alwaysStrict
默認值是true, 開啟這個選項保證輸出的js程式碼處於ECMAScript標準的嚴格模式下, 也就是js文件里的use strict
compilerOptions.exactOptionalProperties
這是typescript4.4中才加入的一個選項, 默認處於不開啟狀態; 開啟此選項, typescript會對可空屬性執行更嚴格的類型檢查, 可空屬性只有在初始化時可以留空為undefined, 但是不能被手動設置為undefined
例如有一個IFoo介面
interface IFoo {
foo?: string;
}
在compilerOptions.exactOptionalProperties = false情況下
const obj: IFoo = {};
obj.foo = '1111';
console.log(obj.foo);
obj.foo = undefined;
console.log(obj.foo);
這段程式碼可以正常編譯通過
但如果開啟compilerOptions.exactOptionalProperties選項後情況就不同了
const obj: IFoo = {};
obj.foo = '1111';
console.log(obj.foo);
// 編譯器會報: Type 'undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target.
obj.foo = undefined;
console.log(obj.foo);
// 這一行會報: Type '{ foo: undefined; }' is not assignable to type 'IFoo' with 'exactOptionalPropertyTypes: true'.
const obj2: IFoo = {
foo: undefined
}
compilerOptions.downlevelIteration
先解釋下什麼是Downleveling? Downleveling是Typescript中的一個術語, 它表示將typescript程式碼轉譯為相對更低版本的javascript
這個標誌位模式是不開啟的.
開啟這個標誌位typescript會生成一個幫助方法來對es6中的for of和數組展開([...arr])語法進行轉譯, 以兼容es3/es5
下面的示例用for of循環並輸出一個包含符號表情的字元串:
const text = `(😜)`;
for (const c of text) {
console.log(c);
}
如果配置typescript的compilerOptions.target選項為es6及以上, 那麼不管有沒有開啟compilerOptions.downlevelIteration, 輸出都是:
(
😜
)
現在把compilerOptions.target設置為es5並關閉compilerOptions.downlevelIteration, 輸出:

控制台中一共有四行, 中間兩行是兩個亂碼, 這跟我們的預期的就不一樣了, 我們預期應該只有三行輸出, 實際結果確是符號表情被分成了兩個亂碼字元輸出了;
這是因為沒開啟compilerOptions.downlevelIteration, typescript處理for of時會直接轉譯成經典的索引迭代(for (var _i = 0, text_1 = text; _i < text_1.length; _i++)), 而符號表情實際上佔了2個位元組的存儲, 所以自然就會將符號表情分成兩個亂碼字元輸出了
現在再開啟compilerOptions.downlevelIteration, 此時typescript處理for...of時會通過輔助方法調用數組對象的Symbol.iterator屬性做一些額外的檢查和處理, 就能正常輸出了

此時生成的js程式碼:
"use strict";
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var e_1, _a;
var text = "(\uD83D\uDE1C)";
try {
for (var text_1 = __values(text), text_1_1 = text_1.next(); !text_1_1.done; text_1_1 = text_1.next()) {
var c = text_1_1.value;
console.log(c);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (text_1_1 && !text_1_1.done && (_a = text_1.return)) _a.call(text_1);
}
finally { if (e_1) throw e_1.error; }
}
更具體的說明看官網: //www.typescriptlang.org/tsconfig#downlevelIteration
compilerOptions.importHelpers
上面介紹了compilerOptions.downlevelIteration選項, 開啟後會對for...of之類的迭代語法糖進行downleveling; typescript進行downleveling時, 會生成一些輔助方法, 默認情況下, 這些輔助程式碼是會直接插入到文件中對應的位置的, 這會生成的javascript存在重複的輔助方法從而造成程式碼文件體積過大的問題.
開啟compilerOptions.importHelpers後, 不在插入具體的輔助方法的程式碼到對應的位置, 而是通過模組導入來引用typescript的輔助方法
看這段typescript
export const foo = () => {
const text = `(😜)`
for (const c of text) {
console.log(c)
}
}
沒開啟compilerOptions.importHelpers時, 生成的javascript:
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
export var foo = function () {
var e_1, _a;
var text = "(\uD83D\uDE1C)";
try {
for (var text_1 = __values(text), text_1_1 = text_1.next(); !text_1_1.done; text_1_1 = text_1.next()) {
var c = text_1_1.value;
console.log(c);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (text_1_1 && !text_1_1.done && (_a = text_1.return)) _a.call(text_1);
}
finally { if (e_1) throw e_1.error; }
}
};
開啟compilerOptions.importHelpers後, 生成的javascript:
import { __values } from "tslib";
export var foo = function () {
var e_1, _a;
var text = "(\uD83D\uDE1C)";
try {
for (var text_1 = __values(text), text_1_1 = text_1.next(); !text_1_1.done; text_1_1 = text_1.next()) {
var c = text_1_1.value;
console.log(c);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (text_1_1 && !text_1_1.done && (_a = text_1.return)) _a.call(text_1);
}
finally { if (e_1) throw e_1.error; }
}
};
compilerOptions.strict
這個strict相關標誌位的一個總開關, 設置為true會啟用全部compilerOptions.strict開頭的選項和其他相關的選項, 如compilerOptions.strictNullChecks, compilerOptions.strictPropertyInitialization, compilerOptions.noImplicitAny ...
開啟此選項後, 依然可以單獨關閉某個具體的以compilerOptions.strict開頭的選項
compilerOptions.strictBindCallApply
開啟此選項後, 調用函數對象的bind call和apply方法時typescript會執行參數類型檢查確保參數類型兼容
const foo = (a: string, b: number) => {
console.log(a, b);
}
// 開啟 strictBindCallApply 後,會報錯
// Argument of type 'number' is not assignable to parameter of type 'string'.
foo.call(undefined, 1, 2)
compilerOptions.strictFunctionTypes
開啟此選項會啟用嚴格的函數類型檢查, 直接看示例:
const foo = (a: string) => {
console.log(a);
}
interface Bar {
(a: string | string[]): void;
}
// 開啟 compilerOptions.strictFunctionTypes, 報錯
// Type '(a: string) => void' is not assignable to type 'Bar'.
const bar:Bar = foo;
修改成:
const foo = (a: string) => {
console.log(a);
}
interface Bar {
(a: string): void;
}
const bar:Bar = foo;才能通過編譯
compilerOptions.strictNullChecks
開啟此選項讓typescript執行嚴格的null檢查
const foo: string|null|undefined = undefined;
// 不開啟 compilerOptions.strictNullChecks ,不會有編譯時錯誤,但是運行時會出現異常(Cannot read properties of undefined )
// 開啟 compilerOptions.strictNullChecks,會出現編譯時錯誤(Object is possibly 'undefined')
console.log(foo.length)
compilerOptions.strictPropertyInitialization
開啟此選項讓typescript嚴格的對象屬性初始化檢查
開啟後這段程式碼會出現編譯時錯誤:
class Foo {
// Property 'foo' has no initializer and is not definitely assigned in the constructor.
foo: string;
}
改成
class Foo {
foo = 'foo';
}
或者
class Foo {
foo: string;
constructor() {
this.foo = '';
}
}
或者在屬性後加感嘆號進行非空斷言(non-null assertion)
class Foo {
foo!: string;
}
方能編譯通過
compilerOptions.noImplicitAny
這個配置還是比較好理解的, 就是開啟此選項後, 如果你聲明一個沒有標註類型的變數, 編譯器會會給你一個編譯時錯誤(Parameter 'arg' implicitly has an 'any' type.)

給foo函數參數arg標註一個string類型可以消除這個錯誤
const foo = (arg: string) => { console.log(arg) };
foo('hello');
另外要注意如果開啟了compilerOptions.strict選項, 那麼這個選項默認就會處於開啟狀態, 除非手動將這個選項配置為false
compilerOptions.noImplicitOverride
typescript 4.3中才引入的配置
這個選項從名字上也是比較好理解的; 開啟此選項保證子類重寫基類的方法時, 必須在方法前加上override關鍵詞
class BillBuilder {
build() {}
}
class MonthBillBuilder extends BillBuilder {
// 開啟 compilerOptions.noImplicitOverride 後,重寫 build 方法必須顯示加上 override 關鍵詞,否則編譯器會報錯:
// This member must have an 'override' modifier because it overrides a member in the base class 'BillBuilder'.
build() {
console.log('Monthly bill')
}
}
正確的寫法:
class BillBuilder {
build() {}
}
class MonthBillBuilder extends BillBuilder {
override build() {
console.log('Monthly bill')
}
}
compilerOptions.noImplicitReturns
開啟這個選項保證編譯時所有條件分支都返回一致的類型, 比如說一個if分支下返回了一個string類型, 但是其他分支沒有進行return, 那麼tsc會給出一個編譯時錯誤(Not all code paths return a value.(7030) )

正確的寫法1:
const hello = (log = false) => {
if (log) {
const text = 'hello';
console.log(text);
return text
}
return '';
}
正確的寫法2:
const hello = (log = false): string | void => {
if (log) {
const text = 'hello';
console.log(text);
return text
}
}
compilerOptions.noImplicitThis
開啟這個選項後, typescript將禁止調用any類型的this
錯誤示例:
function Color() {
// 開啟了ompilerOptions.noImplicitThis的情況下,下面的三行程式碼會出現編譯時錯誤
// 'this' implicitly has type 'any' because it does not have a type annotation.
this.r = 255;
this.g = 255;
this.b = 255;
}
// 如果開啟了 compilerOptions.noImplicitAny , 那麼這一行也是會報編譯時錯誤的:
// 'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.
const c = new Color();
console.log(c.r, c.r, c.b);
正確的寫法:
class Color {
r = 255;
g = 255;
b = 255;
}
const c = new Color();
console.log(c.r, c.r, c.b);
compilerOptions.noPropertyAccessFromIndexSignature
這個配置選項typescript4.2中才引入
從名稱入手來理解這個配置, no property access from index signature, 就是說開啟後禁止通過訪問常規屬性的方法來訪問index signature聲明的屬性
常規屬性通過在對象後加一個.即可訪問如obj.title
什麼是index signature? 直譯過來是索引簽名, 索引簽名語法一般用來聲明介面或類中的未知屬性的, index signature的示例:
// 標註為IFoo類型的對象可以添加任意的字元串鍵值對
interface IFoo {
[key: string]: string;
}
const foo: IFoo = {
bar: '1'
}
console.log(foo['bar']);
// 輸出: 1
開啟compilerOptions.noPropertyAccessFromIndexSignature後的一個錯誤示例:
class Color {
r = 255;
g = 255;
b = 255;
[key: string]: string | number;
}
const c = new Color();
console.log(c.r, c.g, c.b);
// 開啟 compilerOptions.noPropertyAccessFromIndexSignature 的情況下,會有編譯時錯誤:
// Property 'foo' comes from an index signature, so it must be accessed with ['foo'].
console.log(c.foo);
正確的方式應該是通過c['foo']訪問Color對象c上的foo屬性
這個選項的動機是什麼?
不開啟compilerOptions.noPropertyAccessFromIndexSignature直接通過傳統的.符號來訪問索引簽名屬性, 一不小心非常容易造成運行時出現經典的read property of undefined異常, 比如上面的示例如果直接調用c.foo.toString(), 即使開啟了compilerOptions.strictNullChecks編譯仍能通過, 但是運行就會發生異常; 當然也可以顯示標註索引簽名屬性為可空類型配置compilerOptions.strictNullChecks來達到相同的目的
class Color {
r = 255;
g = 255;
b = 255;
[key: string]: string | number | undefined;
}
const c = new Color();
console.log(c.r, c.g, c.b);
// 關閉 compilerOptions.noPropertyAccessFromIndexSignature,開啟 compilerOptions.strictNullChecks
// 仍然可以獲得編譯時的對象屬性可能為空的錯誤:
// Object is possibly 'undefined'.
console.log(c.foo.toString());
compilerOptions.noUncheckedIndexedAccess
這個配置選項typescript4.1中才引入
開啟這個選項, typescript自動給索引簽名語法聲明的屬性補上一個undefined類型; 上面介紹compilerOptions.noPropertyAccessFromIndexSignature時提到可以自己手動給索引簽名語法聲明的屬性加上undefined類型標註達到和開啟compilerOptions.noPropertyAccessFromIndexSignature相同的目的

看上面的圖片中雖然沒有顯示給索引簽名屬性標註undefined, 但是滑鼠懸浮到c.foo上時typescript自動給它加上了undefined
compilerOptions.noUnusedLocals
又是一個很好理解的配置選項, 開啟這個選項, 當typescript發現未使用的局部變數時, 會給出一個編譯時錯誤('<propertyName>' is declared but its value is never read.(6133))
const sayHello = () => {
// 開啟 compilerOptions.noUnusedLocals 後,typescript編譯時會報錯
// 'text' is declared but its value is never read.
const text = 'hello';
console.log('hello');
}
✅正確的做法:
const sayHello = () => {
const text = 'hello';
console.log(text);
}
compilerOptions.noUnusedParameters
和上面的compilerOptions.noUnusedLocals, 不同之處在於局部變數變成了函數參數
❌錯誤示例:
// 開啟 compilerOptions.noUnusedParameters 後,typescript編譯時會報錯
// 'text' is declared but its value is never read.
const sayHello = (text: string) => {
console.log('hello');
}
✅正確的寫法:
// 開啟 compilerOptions.noUnusedParameters 後,typescript編譯時會報錯
// 'text' is declared but its value is never read.
const sayHello = (text: string) => {
console.log(text);
}
compilerOptions.useUnknownInCatchVariables
這個配置選項typescript4.4中才引入
開啟這個選項typescript會將catch語法塊中的err變數當做unknown來處理, 不開啟此選項時, err變數是被當做any類型來處理的, 這很容易造成經典的read property of undefined運行時異常
一個簡單的示例, 不開啟compilerOptions.useUnknownInCatchVariables選項運行時才能發布異常

開啟了compilerOptions.useUnknownInCatchVariables選項, 編譯時立即發現問題

小結
本文主要介紹了typescript中類型檢查相關的配置, typescript還有其他不少配置的, 官網都有詳細的文檔
我對typescript一些看法
我是18年大二階段開始接觸前端相關的程式語言的, 那個時候typescript還沒有這麼流行, 接觸使用學習時還是以js為主的; 此前主主要使用的語言的是C/C++ C#這些強類型語言, 對js存在強烈的抵觸甚至厭惡, 然後接觸到angular時發現它的整個生態都是構建在typescript之上的, 終於遇到了救星; 雖然那個時候剛剛接觸, angular用起來也是一知半解的, 常常被typescript中如何使用js庫之類的小白問題支配, 但是相比要我寫沒有類型註解的javascript, 那時的我依然選擇慢慢摸索解決typescript中如何使用js庫這樣的小白問題, 解決這些問題過程中, 我對js也有了更深入的理解, 慢慢的我甚至可以脫離typescript學會寫javascript了😂typescript的定位是javascript的超集, 與我而言, typescript確實我在javascript上的老師, 沒有typescript, 那個時候的我可能已經放棄學習javascript了, 就像叫不醒一個裝睡的人一樣, 我們永遠也學不會一門自己不想學設置討厭的的程式語言; 也不是說沒有typescript就永遠都不會對javascript產生興趣, 而是產生興趣的時間會延後到不知何時;
typescript是一個良師益友, 不敢想像沒有typescript的世界將會是怎樣😇或許也並不會怎樣, 只不過少了一個愛寫前端的靚仔而已🌚

