TypeScript 基礎與進階使用指南
__Notes
TypeScriptJavaScriptProgrammingStatic TypingType System
什麼是 TypeScript?
TypeScript 是 JavaScript 的超集,添加了靜態型別定義,可以在開發階段捕獲錯誤,提高程式碼質量和可維護性。
- 靜態型別檢查:在編譯時檢查型別錯誤,而不是運行時。
- 更好的開發體驗:提供自動完成、型別推斷和錯誤提示。
- 更好的程式碼組織:通過接口和型別定義,使程式碼結構更清晰。
TypeScript 基礎型別
TypeScript 提供了多種基礎型別,以下是常見的例子:
typescript
// 基本型別
let isDone: boolean = false;
let decimal: number = 6;
let color: string = 'blue';
let list: number[] = [1, 2, 3];
let tuple: [string, number] = ['hello', 10];
// 枚舉
enum Color {
Red,
Green,
Blue,
}
let c: Color = Color.Green;
// Any 和 Unknown
let notSure: any = 4;
let uncertain: unknown = 4;
// Void, Null 和 Undefined
function warnUser(): void {
console.log('This is a warning message');
}
let u: undefined = undefined;
let n: null = null;
any 與 unknown 的差異
TypeScript 中的 any 和 unknown 型別都可以接受任何值,但它們在型別安全性上有重要的區別:
any 型別
any 型別基本上會關閉 TypeScript 的型別檢查:
typescript
let anyValue: any = 10;
anyValue = 'hello';
anyValue = true;
// 以下操作不會產生編譯錯誤,但可能在運行時出錯
anyValue.foo.bar; // 不會報錯
anyValue(); // 不會報錯
anyValue.trim(); // 不會報錯
any 的問題:
- 繞過了型別檢查
- 可能導致運行時錯誤
- 失去了 IDE 的自動完成和提示功能
- 錯誤可能在程式的其他部分才被發現
unknown 型別
unknown 是型別安全的 any:
typescript
let unknownValue: unknown = 10;
unknownValue = 'hello';
unknownValue = true;
// 以下操作會產生編譯錯誤
// unknownValue.foo.bar; // 錯誤:對象的型別為 'unknown'
// unknownValue(); // 錯誤:對象的型別為 'unknown'
// unknownValue.trim(); // 錯誤:對象的型別為 'unknown'
// 必須先進行型別檢查或型別斷言
if (typeof unknownValue === 'string') {
unknownValue.trim(); // 現在可以了
}
// 或使用型別斷言
const stringValue = unknownValue as string;
stringValue.trim(); // 可以,但如果實際不是字符串會在運行時出錯
unknown 的優點:
- 需要先進行型別檢查或型別斷言才能使用
- 強制開發者處理型別安全問題
- 錯誤在編譯時就能被發現
- 保留了型別安全性
何時使用 any 和 unknown
-
使用 unknown 當:
- 你不確定變數的型別
- 你想保持型別安全
- 你計劃在使用前進行型別檢查
-
使用 any 當:
- 你正在快速原型開發
- 你正在遷移 JavaScript 程式碼到 TypeScript
- 你確實需要繞過型別檢查(應該盡量避免)
實際應用示例
typescript
// 處理 API 響應
async function fetchData(url: string): Promise<unknown> {
const response = await fetch(url);
return response.json(); // 返回 unknown 而不是 any
}
// 使用時需要進行型別檢查
async function processUserData() {
const data = await fetchData('/api/users');
// 型別保護
if (data && typeof data === 'object' && 'users' in data && Array.isArray(data.users)) {
// 現在可以安全地使用 data.users
return data.users.map((user) => user.name);
}
return [];
}
在這個例子中,使用 unknown 而不是 any 迫使我們處理可能的型別問題,使程式碼更加健壯。
Type vs Interface
TypeScript 提供了兩種定義型別的方式:type 和 interface。
使用 Interface
Interface 主要用於定義對象的形狀:
typescript
// 定義接口
interface User {
id: number;
name: string;
email?: string; // 可選屬性
readonly createdAt: Date; // 唯讀屬性
}
// 擴展接口
interface Employee extends User {
department: string;
salary: number;
}
// 函數接口
interface SearchFunction {
(source: string, subString: string): boolean;
}
// 實現函數接口
const searchStr: SearchFunction = (src, sub) => src.includes(sub);
使用 Type
Type 可以用於定義聯合型別、交叉型別和其他複雜型別:
typescript
// 聯合型別
type ID = string | number;
// 交叉型別
type AdminUser = User & { permissions: string[] };
// 字面量型別
type Direction = 'up' | 'down' | 'left' | 'right';
// 函數型別
type ClickHandler = (event: MouseEvent) => void;
// 使用型別別名定義對象
type Point = {
x: number;
y: number;
};
何時使用 Type 或 Interface?
-
使用 Interface 當:
- 你需要定義一個對象的形狀
- 你需要擴展該型別
- 你需要聲明合併(同名接口會自動合併)
-
使用 Type 當:
- 你需要使用聯合型別或交叉型別
- 你需要使用條件型別
- 你需要使用映射型別
- 你需要使用元組型別
TypeScript 泛型
泛型允許你創建可重用的組件,這些組件可以與多種型別一起工作:
typescript
// 泛型函數
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函數
let output = identity<string>('myString');
// 或者讓 TypeScript 自動推斷型別
let output2 = identity('myString');
// 泛型箭頭函數
const getFirstElement = <T>(array: T[]): T | undefined => {
return array[0];
};
// 泛型接口
interface GenericResponse<T> {
data: T;
status: number;
message: string;
}
// 使用泛型接口
const userResponse: GenericResponse<User> = {
data: { id: 1, name: 'John', createdAt: new Date() },
status: 200,
message: 'Success',
};
常用工具型別
TypeScript 提供了多種內置的工具型別,幫助你轉換和操作型別:
Partial<T>
將型別的所有屬性設為可選:
typescript
interface User {
id: number;
name: string;
email: string;
}
// 所有屬性變為可選
type PartialUser = Partial<User>;
// 等同於:
// type PartialUser = {
// id?: number;
// name?: string;
// email?: string;
// }
// 實際應用:更新用戶信息
function updateUser(userId: number, updates: Partial<User>) {
// 只需提供要更新的字段
}
updateUser(1, { name: 'New Name' }); // 有效
Required<T>
將型別的所有屬性設為必需:
typescript
interface Config {
host?: string;
port?: number;
secure?: boolean;
}
// 所有屬性變為必需
type RequiredConfig = Required<Config>;
// 等同於:
// type RequiredConfig = {
// host: string;
// port: number;
// secure: boolean;
// }
Pick<T, K>
從型別 T 中選擇一組屬性 K:
typescript
interface User {
id: number;
name: string;
email: string;
password: string;
}
// 只選擇 id 和 name 屬性
type UserBasicInfo = Pick<User, 'id' | 'name'>;
// 等同於:
// type UserBasicInfo = {
// id: number;
// name: string;
// }
// 實際應用:API 響應過濾
function getUserPublicInfo(user: User): UserBasicInfo {
return {
id: user.id,
name: user.name,
};
}
Omit<T, K>
從型別 T 中排除一組屬性 K:
typescript
interface User {
id: number;
name: string;
email: string;
password: string;
}
// 排除 password 屬性
type UserPublicInfo = Omit<User, 'password'>;
// 等同於:
// type UserPublicInfo = {
// id: number;
// name: string;
// email: string;
// }
Record<K, T>
創建一個具有指定鍵型別 K 和值型別 T 的對象型別:
typescript
// 使用字符串作為鍵,User 作為值
type UserRecord = Record<string, User>;
// 使用特定字符串字面量作為鍵
type Roles = 'admin' | 'user' | 'guest';
type RoleConfig = Record<Roles, { permissions: string[] }>;
// 實際應用:配置對象
const rolePermissions: RoleConfig = {
admin: { permissions: ['read', 'write', 'delete'] },
user: { permissions: ['read', 'write'] },
guest: { permissions: ['read'] },
};
Exclude<T, U> 和 Extract<T, U>
Exclude<T, U> 從 T 中排除可分配給 U 的型別,而 Extract<T, U> 從 T 中提取可分配給 U 的型別:
typescript
type T0 = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
type T1 = Extract<'a' | 'b' | 'c', 'a' | 'f'>; // 'a'
// 實際應用:過濾型別
type EventType = 'click' | 'scroll' | 'mousemove';
type MouseEventType = Extract<EventType, 'click' | 'mousemove'>; // 'click' | 'mousemove'
高級型別技巧
條件型別
根據條件選擇不同的型別:
typescript
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// 實際應用:根據輸入型別返回不同的輸出型別
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function createUser() {
return { id: 1, name: 'John' };
}
type User = ReturnType<typeof createUser>; // { id: number, name: string }
映射型別
通過映射現有型別的每個屬性來創建新型別:
typescript
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
id: number;
name: string;
}
type ReadonlyUser = Readonly<User>;
// 等同於:
// type ReadonlyUser = {
// readonly id: number;
// readonly name: string;
// }
實際應用示例
React 函數組件 Props 型別定義
typescript
import React from 'react';
// 使用 interface 定義 Props
interface ButtonProps {
text: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
// 函數組件
const Button: React.FC<ButtonProps> = ({
text,
onClick,
variant = 'primary',
disabled = false
}) => {
return (
<button
onClick={onClick}
className={`btn btn-${variant}`}
disabled={disabled}
>
{text}
</button>
);
};
// 使用組件
const App = () => {
return (
<Button
text="Click me"
onClick={() => console.log('Button clicked')}
variant="primary"
/>
);
};
使用 React Hooks 的型別定義
typescript
import React, { useState, useEffect } from 'react';
// 定義用戶型別
interface User {
id: number;
name: string;
email: string;
}
// 使用泛型 useState
const UserProfile: React.FC = () => {
// 使用泛型指定 state 型別
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch('/api/user/1');
const data = await response.json();
setUser(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
fetchUser();
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>No user found</div>;
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
</div>
);
};
API 響應型別定義
typescript
// 基本用戶型別
interface User {
id: number;
name: string;
email: string;
}
// API 響應型別
interface ApiResponse<T> {
data: T;
status: number;
message: string;
timestamp: string;
}
// 獲取用戶列表響應
type GetUsersResponse = ApiResponse<User[]>;
// 獲取單個用戶響應
type GetUserResponse = ApiResponse<User>;
// 使用示例
const fetchUsers = async (): Promise<User[]> => {
try {
const response = await fetch('/api/users');
const result: GetUsersResponse = await response.json();
return result.data;
} catch (error) {
console.error('Error fetching users:', error);
return [];
}
};
自定義 Hook 的型別定義
typescript
import { useState, useCallback } from 'react';
// 定義自定義 Hook 的返回型別
interface UseToggleReturn {
value: boolean;
toggle: () => void;
setTrue: () => void;
setFalse: () => void;
}
// 自定義 Hook
function useToggle(initialValue = false): UseToggleReturn {
const [value, setValue] = useState<boolean>(initialValue);
const toggle = useCallback(() => setValue(v => !v), []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return { value, toggle, setTrue, setFalse };
}
// 使用自定義 Hook
const ToggleComponent: React.FC = () => {
const { value, toggle } = useToggle();
return (
<button onClick={toggle}>
{value ? 'ON' : 'OFF'}
</button>
);
};
總結
TypeScript 提供了強大的型別系統,可以幫助你寫出更安全、更可維護的程式碼。通過使用適當的型別定義、泛型和工具型別,你可以大大提高開發效率和程式碼質量。
- 基礎型別:為變數提供型別註解
- Interface vs Type:根據需求選擇合適的型別定義方式
- 泛型:創建可重用的型別安全組件
- 工具型別:使用內置工具型別轉換和操作型別
- 高級型別技巧:使用條件型別和映射型別解決複雜問題