參考網站
參考網站
參考網站
本篇來簡單說明一下 typescript 的 tsconfig.json,並記錄一下 typescript 各個資料型別。
TypeScript 是什麼?
❓這個應該是大家都有犯的錯誤,下面這張圖你看到錯誤了嗎?
.
.
.
🅰️:就是寫錯字,record 少了個 s
,有時候即使報 error 了,你還找不到。
JavaScript 就是一個自由的靈魂,你永遠不知道你接了什麼物件,寫的時候很爽,debug 的時候黑人問號。
✅ 而下面這張就引用了 TypeScript 的 interface
來定義物件的型別,讓你在開發的時候就可以馬上知道錯誤,減少了很多 debug 的時間。
tsconfig.json
tsconfig.json 是 typescript 編譯設定的文件。
在目錄下執行以下指令,產生 tsconfig.json:
tsconfig.json 的預設
一開始我們拿到的 tsconfig 檔, 裡面的預設就有這些:
1
2
3
4
5
6
7
8
9
10
| {
"compilerOptions": {
"target": "es5", // 指定編譯生成的JS版本
"module": "commonjs", // 指定生成哪種模組
"strict": true, // 啟用所有嚴格類型檢查選項
"esModuleInterop": true, // 兼容模組導入的方式
"skipLibCheck": true, // 不會檢查引入的函式庫檔案
"forceConsistentCasingInFileNames": true // 確保檔案的大小寫一致
}
}
|
tsconfig.json 還有哪些設定?
除了預設 compilerOptions 的設定外, 還有:
- compileOnSave: 在最上層設定 compileOnSave 屬性,可以讓 IDE 編輯器在儲存檔案的時候根據 tsconfig.json 重新產生編譯檔案。
- compilerOptions: 所有的編譯設定都會寫在 compilerOptions 裡, 我們一開始取得的預設設定就在 compilerOptions。
- include: 指定編譯需要編譯的文件或目錄。
- exclude: 指定編譯器需要排除的文件或文件夾。如默認排除 node_modules 文件夾。
- include 及 exclude 最常見的萬用字元包括:
*
: 表示匹配0至多個字元(不包含分隔符號)?
: 匹配一個相符字元(不包含分隔符號)**/
: 表示匹配所有子資料夾
- extends: 引入其他配置檔案,繼承配置。default 包含當前目錄和子目錄下所有 TypeScript 文件。
- files: files 的值為包含相對或絕對文件路徑的 list,表示需要編譯的文件。default 包含當前目錄和子目錄下所有 TypeScript 文件。若指定 files,則只會編譯指定的檔案。
- references: 指定要引用的專案。有時候為了方便將前端項目和後端 node 項目放在同一個目錄下開發,兩個項目依賴同一個配置文件和通用文件。
- typeAcquisition: 設置自動引入函式庫相關定義文件。包含 3 個子屬性:
- enable: 布林類型,是否開啟自動引入庫類型定義文件(.d.ts),默認為 false;
- include: 數組類型,允許自動引入的函式庫名,如:[“jquery”, “lodash”];
- exculde: 數組類型,排除的函式庫名;
概括
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
| {
"compileOnSave": false, // 編輯器在儲存檔案的時候根據 tsconfig.json 重新產生編譯檔案
"compilerOptions": {
/* 基本選項 */
"target": "es5", // 指定編譯生成的 JS 版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定生成哪種模組: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 編譯需要引入的特定函式庫檔案
"allowJs": true, // 允許編譯 javascript 文件
"checkJs": true, // 報告 javascript 文件中的錯誤
"jsx": "preserve", // 指定 jsx 代碼的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相應的 '.d.ts' 文件
"declarationMap": true, // 生成宣告檔案的 sourceMap
"sourceMap": true, // 生成相應的 '.map' 文件
"outFile": "./", // 將輸出文件合併為一個文件
"outDir": "./", // 指定輸出目錄
"rootDir": "./", // 檔案應該要被放置的位置
"composite": true, // 是否編譯構建引用項目
"tsBuildInfoFile": "./", // 指定文件存儲增量編譯信息,默認為 tsconfig.tsbuildinfo
"removeComments": true, // 刪除編譯後的所有的註釋
"noEmit": true, // 不產生輸出檔案
"importHelpers": true, // 從 tslib 導入輔助工具函數
"isolatedModules": true, // 將每個文件做為單獨的 module(與 'ts.transpileModule' 類似)
/* 嚴格的類型檢查選項 */
"strict": true, // 啟用所有嚴格類型檢查選項
"noImplicitAny": true, // 在表達式和聲明上有隱含的 any 類型時報錯
"strictNullChecks": true, // 啟用嚴格的 null 檢查
"strictFunctionTypes": true, // 啟用檢查 function 型別
"strictBindCallApply": true, // 啟用對 bind、call、apply 更嚴格的型別檢查
"strictPropertyInitialization": true, // 啟用 class 實例屬性的賦值檢查
"noImplicitThis": true, // 當 this 表達式值為 any 類型的時候,生成一個錯誤
"alwaysStrict": true, // 以嚴格模式檢查每個 module,並在每個文件裡加入 'use strict'
/* 額外的檢查 */
"noUnusedLocals": true, // 有未使用的變數時,拋出錯誤
"noUnusedParameters": true, // 有未使用的參數時,拋出錯誤
"noImplicitReturns": true, // 並不是所有 function 裡的代碼都有返回值時,拋出錯誤
"noFallthroughCasesInSwitch": true, // 報告 switch 語句的 fallthrough 錯誤。 (即不允許 switch 的 case 語句貫穿)
"noUncheckedIndexedAccess": true, // 檢查 index signature 屬性是否是 undefined
/* 模組選項 */
"moduleResolution": "node", // 選擇模組解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用於解析非相對模組名稱的基目錄
"paths": {}, // 模組名到基於 baseUrl 的路徑映射的列表
"rootDirs": [], // 根文件夾列表,其組合內容表示項目運行時的結構內容
"typeRoots": [], // 包含類型聲明的文件列表
"types": [], // 需要包含的類型聲明文件名列表
"allowSyntheticDefaultImports": true, // 允許從沒有設置默認導出的模組中默認導入
/* Source Map Options */
"sourceRoot": "./", // 指定調試器應該找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定調試器應該找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成單個 soucemaps 文件,而不是將 sourcemaps 生成不同的文件
"inlineSources": true, // 將代碼與 sourcemaps 生成到一個文件中,要求同時設置了 --inlineSourceMap 或 --sourceMap 屬性
/* 其他選項 */
"experimentalDecorators": true, // 啟用裝飾器
"emitDecoratorMetadata": true, // 為裝飾器提供元數據的支持
/* 進階選項 */
"skipLibCheck": true, // 不會檢查引入的函式庫檔案
"forceConsistentCasingInFileNames": true // 確保檔案的大小寫一致
},
"files":[
"hello.ts" // 若指定 files,則只會編譯指定的 hello.ts 檔案
],
"exclude": [ // 指定編譯器需要排除的文件或文件夾
"node_modules"
],
"extends": "./tsconfig.base.json", // 把基礎配置抽離成 tsconfig.base.json 檔案,然後引入
"references": [ // 指定依賴的程式路徑
{ "path": "./common" }
],
"typeAcquisition": { // 自動引入函式庫相關定義文件(.d.ts)。
"enable": false,
"exclude": ["jquery"],
"include": ["jest"]
}
}
|
TypeScript 資料型別整理表
先總結一些資料型別,接著再來詳細介紹各個型別。
Type | 型別分類 | 說明 | Example |
---|
string | primitive | 字串型別 | |
number | primitive | 數值型別 | |
boolean | primitive | 布林型別 | |
null | primitive | 空值型別,亦可賦值給所有型別(嚴謹模式則無法) | |
undefined | primitive | undefined 型別,亦可賦值給所有型別(嚴謹模式則無法) | |
object | object | 物件型別 | |
array | object | 可使用「型別 + 方括號」或陣列泛型來表示陣列 | |
function | object | 一個函式有輸入和輸出,可以針對參數(輸入)及返回值(輸出)進行型別規範 | |
any | ts | 允許賦值為任意型別 | |
unknown | ts | unknown 和 any 一樣可以接受任何型別賦值,但 any 可以賦值給任何型別,unknown 只能賦值給 any 和自己 | |
void | ts | 沒有任何返回值的函式 | |
never | ts | 表示不應該存在的狀態的型別,一般用於錯誤處理函式 | |
union types | ts | 聯合型別(union type) 使用 | 表示其定義的值可以為多種型別 | |
intersection types | ts | 交集型別(intersection type) 使用 & 表示其定義的值都必須符合多種型別 | |
literal types | ts | 某些特殊的"值"可以當作"型別"來使用 | |
tuple | ts | tuple 就是合併了不同型別的物件 | |
enum | ts | 列舉(enum)型別可以用來管理多個同系列的常數,作為狀態判斷使用 | |
primitive: 原始型別
ts: typescript 的特殊型別
typescript 的特殊型別
any
如果你不希望某些值出現型別檢查錯誤,可以使用 any,他是用來表示允許賦值為任意型別。
❌ 假設我們設定 favoriteNumber 變數的值的型別為 string,若使用數字則會報錯。
1
2
3
| let favoriteNumber: string = 'seven';
favoriteNumber = 7;
//error : Cannot assign to 'favoriteNumber' because it is a constant.
|
✅ 這時候使用 any, 則允許任何型別。
1
2
| let favoriteNumber: any = 'seven';
favoriteNumber = 7;
|
宣告一個變數為任意值之後,對它的任何操作,返回的內容的型別都是任意值。在 any 型別下,可以賦值給任何型別,使用任何屬性和方法,都是被允許的。
但這樣是非常危險,非必要都不會使用 any
,要慎用!
未宣告型別的變數及參數也視為 any 型別
變數如果在宣告的時候,未指定其型別,那麼它會被識別為 any 型別:
1
2
3
| let something; // something: any
something = 7;
something = "seven";
|
用 noImplicitAny,來提醒我使用了 any
如上面所述,未指定型別則被識為 any,如果你想避免這種情況,可以在 tsconfig 設定 noImplicitAny = true
,或是打開 “strict”: true 開啟所有嚴謹模式。他就會提醒你使用了 any 型別。
1
2
3
4
5
| {
// ...
"noImplicitAny": true,
// ...
}
|
unknown
unknow 可以接受任何型別賦值, 有點類似 any 類型, 但使用上比 any 安全,來看下面例子:
1
2
3
4
5
6
7
| function f1(a: any) {
a.b(); // success
}
function f2(a: unknown) {
a.b(); // error: Property 'b' does not exist on type 'unknown'.
}
|
使用 unknow 會報錯,沒有 b 函式的屬性,而 any 則會不會告訴你。
雖然 unknown 和 any 一樣可以接受任何型別賦值,但 any 可以賦值給任何型別,unknown 只能賦值給 any 和自己。
1
2
3
4
5
6
7
8
9
10
| let value: unknown;
let value1: unknown = value; // success
let value2: any = value; // success
let value3: boolean = value; // error
let value4: number = value; // error
let value5: string = value; // error
let value6: object = value; // error
let value7: any[] = value; // error
let value8: Function = value; // error
|
void
JavaScript 沒有空值(void)的概念,在 TypeScript 中,可以用 void 表示沒有任何返回值的函式,如以下例子,這個函式只有 alert,不會 return 任何值。
1
2
3
| function alertName(): void {
alert('My name is iris');
}
|
never
表示不應該存在的狀態的型別,一般用於錯誤處理函式。
1
2
3
| function error(message: string): never {
throw new Error(message);
}
|
此外,以下面例子,參數使用 Union Types(聯合型別),當參數被判斷沒有其他型別時,也會被視為 never。
1
2
3
4
5
6
7
8
9
| function fn(x: string | number) {
if (typeof x === "string") {
// do something
} else if (typeof x === "number") {
// do something else
} else {
x; // has type 'never'!
}
}
|
Union Types (聯合型別)
聯合型別(Union Types)表示取值可以為多種型別中的其中一種。跟JavaScript ||
or 的概念是一樣的。
✅ 聯合型別使用 |
分隔每個型別。
允許 id 的型別是 string 或者 number,但是不能是其他型別。
1
2
3
4
5
| function printId(id: number | string) {
console.log("Your ID is: " + id);
}
printId(101); // success
printId("202"); // success
|
❌ 若使用 boolean 值,就會報錯。
1
2
| printId(false); // error
// error : Argument of type 'false' is not assignable to parameter of type 'string | number'.Type 'false' is not assignable to type 'number'.
|
存取聯合型別的屬性或方法
當 TypeScript 不確定一個聯合型別的變數到底是哪個型別的時候,我們只能存取此聯合型別的所有型別裡共有的屬性或方法。
✅ 使用兩種型別都有的屬性時,沒問題。
1
2
3
| function printId(id: number | string) {
console.log(id.toString());
}
|
❌ 如果使用不同屬性,則會報錯,toUpperCase
屬性無法適用於數值型別。
1
2
3
4
5
6
| function printId(id: number | string) {
console.log(id.toUpperCase());
}
// error: Property 'toUpperCase' does not exist on type 'string | number'.
// Property 'toUpperCase' does not exist on type 'number'.
|
若要解決不同情境,可以使用 typeof
去做判斷。官網將這些情況限縮某個具體型別上的行為,稱作為 Narrowing
。
1
2
3
4
5
6
7
8
9
10
| function printId2(id: number | string) {
if (typeof id === "string") {
// 型別為字串才 toUpperCase
console.log(id.toUpperCase());
} else {
// 其他自動判定為 number 型別
console.log(id);
}
}
printId2("ABC");
|
若是陣列型別與其他型別聯合,可以使用 Array.isArray()
來檢查傳入的值是否為一個 Array。
1
2
3
4
5
6
7
8
| function welcomePeople(x: string[] | string) {
if (Array.isArray(x)) {
console.log("Hello, " + x.join(" and "));
} else {
console.log("Welcome lone traveler " + x);
}
}
welcomePeople(["a","b","c"]);
|
Intersection Types (交集型別)
Intersection Types (交集型別) 跟 JavaScript 的 &&
and 概念相同, 使用 &
表示其定義的值都必須同時符合兩種型別。
❌ 以下面的例子,Intersection 在 primitive type(原始型別)中使用,是無法同時滿足兩種型別的,會被認定為 never
型別。
1
2
3
4
5
6
7
| function printId(id: number & string) {
console.log("Your ID is: " + id);
}
printId(101); // error
printId("202"); // error
// error: Argument of type 'number' is not assignable to parameter of type 'never'.
|
✅ 主要用來組合現有的型別,若都沒符合兩種型別,則會報錯提醒。而且像下面的例子,ColorfulCircle 需滿足 Colorful 及 Circle 型別。 而且 TS 很聰明,radius 寫錯字寫成 raidus,complier 也會報錯提醒!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
//用 type aliases 宣告 ColorfulCircle 型別,需滿足 Colorful 及 Circle 型別
type ColorfulCircle = Colorful & Circle;
//帶入的參數需滿足 ColorfulCircle 型別
function draw(circle: ColorfulCircle) {
console.log(`Color was ${circle.color}`);
console.log(`Radius was ${circle.radius}`);
}
draw({ color: "blue", radius: 42 }); // ok
draw({ color: "red", raidus: 42 }); // error
// error: Argument of type '{ color: string; raidus: number; }' is not assignable to parameter of type 'Colorful & Circle'.
// Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?
|
Literal Types 字面值型別
string literal types
就是值的表現方式,**某些特殊的"值"可以當作"型別"**來使用,用來約束取值只能是某幾個字串中的一個。如下方讓 x 變數的字面值為"hello"。
1
2
3
| let x: "hello" = "hello";
x = "hello"; // success
x = "howdy"; // Type '"howdy"' is not assignable to type '"hello"'
|
但很少情況會使用到一個變數只有一個值,我們可以將想要的值結合起來, 如 alignment 給予 3 個字面值,其參數要符合其中一個。如果不符合這 3 個值的其中一個或寫錯字,都會報錯。
1
2
3
4
5
6
| function printText(s: string, alignment: "left" | "right" | "center") {
console.log(`${s} placed at the ${alignment}`)
}
printText("Hello, world", "left"); // success
printText("G'day, mate", "centre"); // error
// error: Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.
|
numeric literal types
除了文字上,數字的操作也一樣。
執行 compare 函式的回傳值只能 -1、0 和 1:
1
2
3
| function compare(a: string, b: string): -1 | 0 | 1 {
return a === b ? 0 : a > b ? 1 : -1;
}
|
non-literal types
非字面值型別也可以結合操作:
1
2
3
4
5
6
7
8
9
10
| interface Options {
width: number;
}
function configure(x: Options | "auto") {
console.log(x);
}
configure({ width: 100 }); // success
configure("auto"); // success
configure("automatic"); // 不符 Options 及 "auto"
// error: Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.
|
Tuple 元組
Tuple 就是合併了不同型別的物件。
定義一對值分別為 string 和 number 的元組:
1
| const wayne: [string, number] = ['wayne', 18];
|
可以針對型別有的屬性進行使用、賦值。但當直接對元組型別的變數進行初始化或者賦值,需提供所有項目。如果要新增不同型別的項目也是無法的:
1
2
3
4
5
6
7
8
9
10
| let wayne: [string, number];
wayne = ['wayne', 18]; // 如果只有宣告 wayne 沒賦值,會是undefined,tsconfig strictNullChecks 打開的話會報錯提醒
wayne[0] = 'wayne'; // success
wayne[1] = 25; // success
wayne[0].slice(1); // success
wayne[1].toFixed(2); // success
wayne.push('male'); // success
wayne = ['wayne lin']; // error: Property '1' is missing in type '[string]' but required in type '[string, number]'.
wayne.push(true); // error: Argument of type 'true' is not assignable to parameter of type 'string | number'.
|
enum
列舉(Enums)型別用於取值被限定在一定範圍內的場景,可以用來管理多個同系列的常數,作為狀態判斷使用。比如一週只能有七天,顏色限定為紅綠藍等。也可以自定義來表示每一個值。
Numeric Enums
使用 enum
關鍵字來定義:
1
| enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat };
|
這時候我們來看編譯後,列舉成員會被賦值為從 0 開始自動遞增的數字,同時也會對列舉值到列舉名進行反向對映:
1
2
3
4
5
6
7
8
9
10
| var Days;
(function (Days) {
Days[Days["Sun"] = 0] = "Sun";
Days[Days["Mon"] = 1] = "Mon";
Days[Days["Tue"] = 2] = "Tue";
Days[Days["Wed"] = 3] = "Wed";
Days[Days["Thu"] = 4] = "Thu";
Days[Days["Fri"] = 5] = "Fri";
Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));
|
手動賦值
可以手動去定義不同的值。
1
| enum Days { Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat };
|
編譯後:
1
2
3
4
5
6
7
8
9
10
| var Days;
(function (Days) {
Days[Days["Sun"] = 7] = "Sun";
Days[Days["Mon"] = 1] = "Mon";
Days[Days["Tue"] = 2] = "Tue";
Days[Days["Wed"] = 3] = "Wed";
Days[Days["Thu"] = 4] = "Thu";
Days[Days["Fri"] = 5] = "Fri";
Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));
|
要小心覆蓋的情況
如果中間突然指定了了一個值,那他會從當前的值從新開始計算。如下, Days[3]
的值先是 “Sun”,而後又被 “Wed” 覆蓋了。這個 TypeScript 是不會報錯的,所以要特別注意。
1
2
3
4
5
6
| enum Days { Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat };
console.log(Days["Sun"] === 3); // true
console.log(Days["Wed"] === 3); // true
console.log(Days[3] === "Sun"); // false
console.log(Days[3] === "Wed"); // true
|
可使用型別斷言(Type Assertion)來定義
手動賦值的列舉項其中一個不想是數字,此時可以使用 any 型別斷言來讓 TypeScript 無視型別檢查。
1
| enum Days { Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>"S" };
|
編譯後:
1
2
3
4
5
6
7
8
9
10
| var Days;
(function (Days) {
Days[Days["Sun"] = 7] = "Sun";
Days[Days["Mon"] = 8] = "Mon";
Days[Days["Tue"] = 9] = "Tue";
Days[Days["Wed"] = 10] = "Wed";
Days[Days["Thu"] = 11] = "Thu";
Days[Days["Fri"] = 12] = "Fri";
Days[Days["Sat"] = "S"] = "Sat";
})(Days || (Days = {}));
|
String Enums 字串列舉
字串 enums 沒有 number enums 可以自動遞增的行為,但字串 enum 自行定義是有他的語意在的。
1
2
3
4
5
6
| enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
|
Heterogeneous Enums 異構列舉
雖然 enum 可以混合數字與字串,但盡量不要寫這種寫法:
1
2
3
4
| enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}
|
Computed and constant members 常數項和計算所得項
列舉項有兩種型別:常數項
(constant member)和計算所得項
(computed member)。
前面我們所舉的例子都是常數項,一個典型的計算所得項的例子如下,“blue”.length 就是一個計算所得項:
1
2
| enum Color { Red, Green, Blue = "blue".length };
console.log(Color.Blue); // 4
|
如何定義是常數項,可參考官網。
像是二元運算子 <<
、|
、&
等都歸為常數項:
1
2
3
4
5
6
7
8
9
| enum FileAccess {
// constant members
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// computed member
G = "123".length,
}
|
Const Enums 常數列舉
可以使用 const enum 定義的列舉型別:
1
2
3
4
5
6
7
8
| const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
|
常數列舉與普通列舉的區別是,它會在編譯階段被刪除,並且不能包含 computed member (計算成員),只能是常數項 (constant member)。
編譯後:
1
| var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
|
Ambient Enums 外部列舉
外部列舉 (Ambient Enums) 是使用 declare enum
定義的列舉型別:
1
2
3
4
5
6
7
8
| declare enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
|
declare 定義的型別只會用於編譯時的檢查,編譯結果中會被刪除。
編譯後:
1
| var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
|