[TS] TypeScript Getting Started
此篇為各筆記之整理,非原創內容,資料來源可見下方連結與文後參考資料。
CLI
$ npm init -y # 初始化 npm
$ npm install -D typescript # 安裝 typescript
$ npx tsc --init # 產生 TS 設定檔 tsconfig.json
$ npx ts-node index.ts
# 將 TS 編譯成 JS
$ tsc # Run a compile based on a backwards look through the fs for a tsconfig.json
$ tsc index.ts
$ tsc src/*.ts # Transpile any .ts files in the folder src, with the default settings
$ tsc --watch # 常駐使用語法檢測
- ts-node-dev:當檔案有變更時能夠自動重啟(不是自動重新打包)
- ts-node:不需重新打包就可以直接執行 ts 檔
For Development
透過 nodemon 搭配 ts-node 自動 reload 與編譯
How to watch and reload ts-node when TypeScript files change @ stackOverflow
在根目錄建立 nodemon.json
,並撰寫以下設定:
// nodemon.json
{
"watch": ["src"],
"ext": "ts,json",
"ignore": ["src/**/*.spec.ts"],
"exec": "npx ts-node ./src/index.ts"
}
使用 ts-node 的注意事項
使用 ts-node
時需要留意在 tsconfig.json
中的 module
是否有設定為 "module": "commonjs"
,否則會出現錯誤訊息:
Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
但如果根據它的建議修改,會出現另一個錯誤:
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for
正確的解決方式只需要把 tsconfig.json
中的 module
設為 commonjs
即可(參考:ts-node execute typescript with module import and module defined)。
概念
- 為某變數定義型別的方式稱作「型別註記(type annotations)」,又稱作 type declarations,例如,
let firstName: string
。 - 當我們宣告變數而沒有使用型別註記時,TypeScript 會自動進行「型別推論(type inference)」的動作,也就是幫每個變數進行預設的型別註記。
- 有時你會明確知道某變數的型別,但 TypeScript 並不知道這些資訊,這時候可以使用「型別斷言(type assertion)」
Type
readonly properties
keywords: Readonly
type Todo = {
readonly id: number;
readonly text: string;
readonly done: boolean;
};
// 等同於
type Todo = Readonly<{
id: number;
text: string;
done: boolean;
}>;
// 讓陣列不能被改變 readonly Array of Todo
type Todos = readonly Todo[];
literal types (exact value)
type CompletedTodo = Readonly<{
id: number;
text: string;
done: true; // 在定義 type 的時候,直接定義要使用某個值
}>;
Intersection and Union types
keywords: &
, |
intersection type
type A = { a: number };
type B = { b: string };
// This intersection type…
type AAndB = A & B;
// 等同於
type AAndB = {
a: number;
b: string;
};
如果兩個屬性名稱相同,比較嚴格的規則(true
)會把較不嚴格的規則(boolean
)覆蓋(取交集的概念);但若兩個規則互斥無法取得交集時,則會變成 never
:
// B 的規則比 A 更嚴格
type A = { foo: boolean };
type B = { foo: true };
// 取得交集之後
type AAndB = A & B;
// 等同於
type AAndB = { foo: true };
透過這種方式,我們可以用原有的型別定義新的型別:
type Todo = Readonly<{
id: number;
text: string;
done: boolean;
}>;
// Override the done property of Todo
type CompletedTodo = Todo & {
readonly done: true;
};
Union Type
// 建立一個 Foo type,它是 number 和 string 的交集
type Foo = number | string;
// 使用 Foo 型別時,可以同時是 number 或 string
const a: Foo = 1;
const b: Foo = 'hello';
也可以搭配 literal type
:
type Place = 'home' | 'work' | { custom: string };
Interface
Excess Property Checks:Introduction to TypeScript Interfaces — Object Literals and Function Types
- Optional Properties (
?
):可以不帶入該屬性 - Readonly Properties (
readonly
):賦值之後將不能更改該值 - Readonly Array(ReadonlyArray<T>
) -const
vsreadonly
:const
是針對變數使用,readonly
是針對物件屬性 - Excess Property Checks:將 object literals 指派到其他變數或當作參數帶入時,這個 object literals 會被做更多的檢驗(excess property checking)
- Function Types:使用 interface 定義 function
- Indexable Types:使用 interface 定義 index 的型別和對應元素的型別
// Function Types
interface SearchFunc {
(source: string, subString: string): boolean;
}
// Indexable Types
interface StringArray {
[index: number]: string;
}
// Read only string array
interface ReadonlyStringArray {
readonly [index: number]: string;
}
// Optional Properties:可以不帶入該屬性
interface SquareConfig {
color?: string;
width?: number;
}
// Readonly Properties:賦值之後將不能更改該值
interface Point {
readonly x: number;
readonly y: number;
}
// Readonly Array:陣列的元素不能被修改
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
/**
* Excess Property Checks
**/
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
return { color: config.color || 'red', area: config.width || 20 };
}
// 因為直接把 object literal 帶入參數,會經過 excess property checks
let errorSquare = createSquare({ colour: 'blue', width: 100 }); // 會報錯,
// 使用 type assertion
let typeAssertionSquare = createSquare({
width: 100,
opacity: 0.5,
} as SquareConfig); // 不會報錯
// 不建議這樣用,繞過 excess property checks
let square = { colour: 'blue', width: 100 };
let mySquare = createSquare(square); // 不會報錯
// 建議用法:
// 當可能會有額外參數時,比較好的做法,只要帶入的屬性不是 color 或 width,都可以被接受(不論型別)
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
Type Alias
Interfaces vs Types alias @ React TypeScript: Basics and Best Practices
透過 type aliases 並不是真的建立一個新的 Type,而是建立一個新的「名稱(name)」來指稱到這個 type。
ESLint
ESLint > @typescript-eslint @ pjchender.dev
常見問題
使用 Type 或 Interface?
根據 TypeScript 的建議,在多數的情況下除非有功能是需要 type
才能使用到的,否則就使用 interface
。
資料來源
- TypeScript Tutorial for JS Programmers Who Know How to Build a Todo App
- React TypeScript: Basics and Best Practices