跳至主要内容

[TS] TypeScript Getting Started

此篇為各筆記之整理,非原創內容,資料來源可見下方連結與文後參考資料。

CLI

Compiler Options

$ 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.

ts-node

但如果根據它的建議修改,會出現另一個錯誤:

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for

ts-node

正確的解決方式只需要把 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 vs readonlyconst 是針對變數使用,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

資料來源

Giscus