TypeScript 課程 – 變數與常數宣告(Variables)
主題:顯式型別註記(Type Annotation)
簡介
在 JavaScript 中,變數的型別是由執行時的值決定的,這讓程式在大型專案或多人協作時容易出現不可預期的錯誤。TypeScript 透過 顯式型別註記(type annotation)讓開發者在編寫程式碼的同時,就能告訴編譯器「這個變數應該是什麼型別」,從而在編譯階段就捕捉到潛在的型別問題。
對於剛接觸 TypeScript 的新手而言,了解何時、如何正確地寫型別註記,是提升程式碼可讀性、可維護性以及減少除錯成本的關鍵一步。本章節將從基本概念出發,透過實作範例說明 顯式型別註記 的寫法與最佳使用時機,並提供常見陷阱與實務建議,幫助你在日常開發中善用 TypeScript 的型別系統。
核心概念
1. 為什麼要使用顯式型別註記?
| 優點 | 說明 |
|---|---|
| 提前發現錯誤 | 編譯時即能檢查型別不匹配,避免執行時例外。 |
| 提升 IDE 效能 | 具備完整型別資訊的程式碼,編輯器能提供更精準的自動完成與跳轉。 |
| 增進文件化 | 型別即是文件,閱讀程式碼時不必額外查閱說明文件。 |
| 加強團隊協作 | 明確的型別宣告讓不同開發者對變數的預期一致。 |
註:即使 TypeScript 擁有型別推論(type inference)功能,在公共 API、函式簽名或複雜結構上仍建議使用顯式註記,以避免推論錯誤或不易理解的情況。
2. 基本語法
let name: string = 'Alice'; // 變數 name 必須是 string
const age: number = 30; // 常數 age 必須是 number
:之後接型別名稱(string、number、boolean、any、unknown等)。let用於可變的變數,const用於不可變的常數。- 型別註記 只能寫在變數宣告時,若在賦值時才加入型別,會產生語法錯誤。
3. 常見型別的顯式註記
| 型別 | 範例 | 說明 |
|---|---|---|
string |
let title: string = 'Hello'; |
文字資料 |
number |
let price: number = 99.9; |
整數或浮點數 |
boolean |
let isActive: boolean = true; |
真偽值 |
array |
let tags: string[] = ['ts', 'js']; |
陣列,可寫為 Array<string> |
tuple |
let point: [number, number] = [10, 20]; |
固定長度且每個元素型別固定的陣列 |
enum |
enum Color { Red, Green, Blue }let c: Color = Color.Green; |
列舉型別 |
object |
let user: { name: string; age: number } = { name: 'Bob', age: 25 }; |
具體的物件結構 |
any |
let data: any = fetchData(); |
任意型別(盡量避免) |
unknown |
let input: unknown = getUserInput(); |
不確定但需先做型別檢查才能使用 |
4. 程式碼範例
範例 1:函式參數與回傳值的型別註記
// 這個函式接受兩個 number,回傳它們的總和
function add(a: number, b: number): number {
return a + b;
}
// 使用時若傳入非 number,編譯器會直接報錯
// const result = add('1', 2); // ❌ TypeScript Error
const result = add(1, 2); // ✅ 正確
重點:函式的參數與回傳值都可以(且應該)加上型別註記,讓呼叫端明確知道預期的資料型別。
範例 2:物件屬性的型別註記
interface Product {
id: number;
name: string;
price: number;
tags?: string[]; // 可選屬性
}
// 建立符合介面的物件
const book: Product = {
id: 101,
name: '深入淺出 TypeScript',
price: 350,
// tags 可以不寫
};
說明:使用
interface或type定義結構,然後在變數宣告時直接套用,這是顯式型別註記在大型專案中的常見寫法。
範例 3:陣列與 Tuple
// 字串陣列
let fruits: string[] = ['apple', 'banana', 'cherry'];
// 使用泛型寫法
let numbers: Array<number> = [1, 2, 3, 4];
// Tuple:固定長度且每個元素型別不同
let response: [number, string] = [200, 'OK'];
// response = [404, 'Not Found']; // ✅ 合法
// response = ['error', 500]; // ❌ 型別不匹配
範例 4:函式表達式與 Arrow Function
// 傳統函式寫法
const multiply: (x: number, y: number) => number = function (x, y) {
return x * y;
};
// Arrow Function 寫法
const divide: (x: number, y: number) => number = (x, y) => x / y;
// 呼叫
const product = multiply(3, 4); // 12
const quotient = divide(10, 2); // 5
技巧:在變數上直接寫型別
(x: number, y: number) => number,可以一次描述參數與回傳型別,讓函式的意圖更清晰。
範例 5:使用 unknown 與型別保護
function parseJson(json: string): unknown {
return JSON.parse(json);
}
const data = parseJson('{"name":"Tom","age":30}');
// 必須先做型別檢查才能使用
if (typeof data === 'object' && data !== null && 'name' in data) {
// 這裡 TypeScript 仍認為 data 為 unknown,需要斷言
const name = (data as { name: string }).name;
console.log(name); // Tom
}
重點:
unknown比any更安全,必須先進行型別縮減(type guard)才能存取屬性或方法。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
過度使用 any |
失去型別檢查的好處,等於回到純 JavaScript。 | 盡量使用具體型別或 unknown,必要時再用類型斷言。 |
| 忘記為函式回傳值加註記 | 編譯器會依賴推論,若邏輯變更可能產生不易發現的錯誤。 | 為所有公開函式明確標註回傳型別。 |
在 let 變數上寫 any |
變數可被隨意改寫,導致型別不一致。 | 使用 const 搭配具體型別,或在需要變更時使用聯合型別(union)。 |
忽略可選屬性 (?) 的處理 |
直接存取未定義屬性會產生編譯錯誤。 | 加上型別保護或使用空值合併 (??)。 |
忘記 as 斷言的安全性 |
斷言過度會掩蓋真實錯誤。 | 只在確定資料結構時使用,盡量配合型別保護。 |
最佳實踐
- 先寫介面或型別別名:在實作前先定義
interface/type,再在變數上套用。 - 盡量使用
const:不變的值使用const,讓編譯器自動推斷為字面量型別(literal type)。 - 結合型別推論:在簡單情況下讓 TypeScript 推論,但在公共 API、函式簽名或複雜結構上仍使用顯式註記。
- 使用
readonly:對於不允許修改的屬性,使用readonly以防止意外變更。 - 加入 JSDoc:即使有型別,適當的註解仍能說明業務意圖,提高可讀性。
實際應用場景
1. 前端表單資料驗證
在 React 或 Vue 中,表單的輸入值往往是字串,但後端需要特定型別(如 number、Date)。利用顯式型別註記可以提前捕捉轉型錯誤:
interface LoginForm {
email: string;
password: string;
rememberMe?: boolean;
}
function submit(form: LoginForm): void {
// 這裡 form 的結構已被明確限定
api.post('/login', form);
}
2. Node.js 後端 API 回傳型別
在 Express 或 NestJS 中,回傳的 JSON 需要固定結構,使用型別註記可避免前端解析錯誤:
interface ApiResponse<T> {
success: boolean;
data: T;
error?: string;
}
function getUser(id: number): Promise<ApiResponse<User>> {
return db.findUser(id).then(user => ({
success: true,
data: user,
}));
}
3. 大型團隊協作的程式庫開發
當開發公共套件或 UI 元件庫時,提供完整的型別宣告是必備。顯式型別註記讓使用者在 IDE 中即能看到每個屬性的型別,降低學習成本:
export type ButtonProps = {
label: string;
disabled?: boolean;
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
};
export const Button: React.FC<ButtonProps> = ({ label, disabled, onClick }) => (
<button disabled={disabled} onClick={onClick}>{label}</button>
);
總結
- 顯式型別註記 是 TypeScript 的核心特性之一,能在編譯階段即捕捉錯誤、提升 IDE 體驗,並讓程式碼自帶文件。
- 在 變數、常數、函式參數與回傳值、物件結構 等關鍵位置加入型別註記,可大幅降低後期除錯成本。
- 避免過度使用
any,善用unknown、readonly、interface/type,並配合型別保護提升安全性。 - 實務上,從 前端表單驗證、後端 API 回傳 到 公共套件開發,顯式型別註記皆能提供明確的合約,讓團隊協作更順暢。
掌握了顯式型別註記的寫法與最佳實踐,你將能在 TypeScript 專案中寫出更可靠、更易維護的程式碼。祝你在 TypeScript 的旅程中越走越遠,寫出乾淨且安全的程式!