TypeScript 型別註解 (Type Annotations)
型別註解 (Type Annotations) 是 TypeScript 最核心的功能之一。透過在程式碼中加入型別標記,我們可以明確指定變數、參數和回傳值的型別,讓 TypeScript 編譯器進行型別檢查。
變數型別註解
在變數名稱後加上 : 型別 來標註型別:
// 基本型別
let name: string = '小明';
let age: number = 25;
let isActive: boolean = true;
// 陣列
let numbers: number[] = [1, 2, 3];
let names: string[] = ['Alice', 'Bob'];
// 物件
let user: { name: string; age: number } = {
name: '小明',
age: 25,
};
函式型別註解
參數型別
在參數名稱後加上型別:
function greet(name: string) {
console.log(`Hello, ${name}!`);
}
greet('小明'); // OK
// greet(123); // 錯誤:型別 'number' 的引數不可指派給型別 'string' 的參數
回傳值型別
在參數列表後加上回傳型別:
function add(a: number, b: number): number {
return a + b;
}
function greet(name: string): string {
return `Hello, ${name}!`;
}
function log(message: string): void {
console.log(message);
// 沒有回傳值
}
箭頭函式
// 完整標註
const add: (a: number, b: number) => number = (a, b) => a + b;
// 只標註參數,讓 TypeScript 推論回傳型別
const multiply = (a: number, b: number) => a * b;
// 明確標註回傳型別
const divide = (a: number, b: number): number => a / b;
可選參數與預設值
可選參數
使用 ? 標記可選參數:
function greet(name: string, greeting?: string) {
if (greeting) {
console.log(`${greeting}, ${name}!`);
} else {
console.log(`Hello, ${name}!`);
}
}
greet('小明'); // Hello, 小明!
greet('小明', '早安'); // 早安, 小明!
可選參數的型別會自動變成
型別 | undefined,所以上例中 greeting 的型別是 string | undefined。預設參數
function greet(name: string, greeting: string = 'Hello') {
console.log(`${greeting}, ${name}!`);
}
greet('小明'); // Hello, 小明!
greet('小明', 'Hi'); // Hi, 小明!
有預設值的參數不需要加 ?,TypeScript 會自動推論它是可選的。
物件型別註解
內聯物件型別
function printUser(user: { name: string; age: number; email?: string }) {
console.log(`${user.name}, ${user.age} 歲`);
if (user.email) {
console.log(`Email: ${user.email}`);
}
}
printUser({ name: '小明', age: 25 });
printUser({ name: '小華', age: 30, email: 'hua@example.com' });
唯讀屬性
使用 readonly 標記唯讀屬性:
function printPoint(point: { readonly x: number; readonly y: number }) {
console.log(`(${point.x}, ${point.y})`);
// point.x = 10; // 錯誤:無法指派給 'x',因為它是唯讀屬性
}
函式型別
函式本身也可以作為型別:
// 定義函式型別
type MathOperation = (a: number, b: number) => number;
// 使用函式型別
let add: MathOperation = (a, b) => a + b;
let subtract: MathOperation = (a, b) => a - b;
// 作為參數
function calculate(a: number, b: number, operation: MathOperation): number {
return operation(a, b);
}
console.log(calculate(10, 5, add)); // 15
console.log(calculate(10, 5, subtract)); // 5
回呼函式型別
// 定義接受回呼函式的函式
function fetchData(url: string, callback: (data: string) => void) {
// 模擬非同步操作
setTimeout(() => {
callback('資料內容');
}, 1000);
}
fetchData('https://api.example.com', (data) => {
console.log(data);
});
剩餘參數 (Rest Parameters)
function sum(...numbers: number[]): number {
return numbers.reduce((acc, n) => acc + n, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
this 的型別
在某些情況下,需要明確標註 this 的型別:
interface User {
name: string;
greet(this: User): void;
}
const user: User = {
name: '小明',
greet() {
console.log(`Hello, I'm ${this.name}`);
},
};
user.greet(); // Hello, I'm 小明
// const greet = user.greet;
// greet(); // 錯誤:'this' 的型別不正確
函式重載 (Function Overloads)
TypeScript 允許定義多個函式簽名:
// 重載簽名
function format(value: string): string;
function format(value: number): string;
function format(value: Date): string;
// 實作簽名
function format(value: string | number | Date): string {
if (typeof value === 'string') {
return value.toUpperCase();
} else if (typeof value === 'number') {
return value.toFixed(2);
} else {
return value.toISOString();
}
}
console.log(format('hello')); // HELLO
console.log(format(3.14159)); // 3.14
console.log(format(new Date())); // 2024-12-10T...
型別斷言 (Type Assertion)
當你比 TypeScript 更清楚值的型別時,可以使用型別斷言:
// 使用 as 語法 (推薦)
let value: unknown = 'hello';
let length: number = (value as string).length;
// 使用角括號語法 (在 JSX 中不能使用)
let length2: number = (<string>value).length;
型別斷言只是告訴編譯器「我知道這個值的型別」,不會進行任何型別轉換。如果斷言錯誤,執行時可能會出錯。
常見錯誤與最佳實踐
避免過度標註
TypeScript 有強大的型別推論,不需要每個地方都加上型別:
// 不必要的標註
let name: string = '小明'; // TypeScript 可以推論出 string
// 只在需要時標註
let name = '小明'; // 自動推論為 string
函式回傳值建議標註
雖然可以推論,但明確標註回傳型別有助於:
- 文件化函式的意圖
- 及早發現實作錯誤
// 明確的回傳型別有助於發現錯誤
function getUser(id: number): { name: string; age: number } {
// 如果忘記回傳 age,編譯器會報錯
return { name: '小明', age: 25 };
}
避免使用 any
// 不好:使用 any
function process(data: any) {
return data.foo.bar; // 沒有型別檢查
}
// 好:使用具體型別或 unknown
function process(data: { foo: { bar: string } }) {
return data.foo.bar; // 有型別檢查
}