TypeScript 最佳实践:构建类型安全的应用
TypeScript 为 JavaScript 带来了静态类型检查,帮助我们在开发阶段发现错误,提高代码质量。本文将分享一些 TypeScript 开发中的最佳实践。
基础类型定义
使用接口定义对象类型
// ✅ 推荐:使用接口定义对象结构
interface User {
id: number;
name: string;
email: string;
avatar?: string; // 可选属性
readonly createdAt: Date; // 只读属性
}
// ✅ 推荐:扩展接口
interface AdminUser extends User {
permissions: string[];
lastLogin: Date;
}
使用联合类型和字面量类型
// ✅ 推荐:使用字面量类型限制值的范围
type Theme = "light" | "dark" | "auto";
type Status = "pending" | "success" | "error";
interface ApiResponse<T> {
data: T;
status: Status;
message: string;
}
泛型的使用
函数泛型
// ✅ 推荐:使用泛型提高函数复用性
function createApiClient<T>(baseURL: string): ApiClient<T> {
return new ApiClient<T>(baseURL);
}
// ✅ 推荐:约束泛型
function updateEntity<T extends { id: number }>(
entity: T,
updates: Partial<T>
): T {
return { ...entity, ...updates };
}
实用工具类型
// 使用内置工具类型
interface User {
id: number;
name: string;
email: string;
password: string;
}
// Partial - 所有属性变为可选
type UserUpdate = Partial<User>;
// Pick - 选择特定属性
type UserProfile = Pick<User, "id" | "name" | "email">;
// Omit - 排除特定属性
type CreateUser = Omit<User, "id">;
// Record - 创建键值对类型
type UserRoles = Record<string, string[]>;
高级类型技巧
条件类型
// 条件类型示例
type ApiResponse<T> = T extends string ? { message: T } : { data: T };
// 实用的条件类型
type NonNullable<T> = T extends null | undefined ? never : T;
映射类型
// 创建只读版本
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 创建可选版本
type Optional<T> = {
[P in keyof T]?: T[P];
};
React 中的 TypeScript
组件 Props 类型定义
// ✅ 推荐:明确定义 Props 类型
interface ButtonProps {
children: React.ReactNode;
variant?: "primary" | "secondary" | "danger";
size?: "small" | "medium" | "large";
disabled?: boolean;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
}
function Button({
children,
variant = "primary",
size = "medium",
disabled = false,
onClick,
}: ButtonProps) {
return (
<button
className={`btn btn-${variant} btn-${size}`}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
}
Hooks 类型定义
// useState 类型推断
const [count, setCount] = useState(0); // 自动推断为 number
const [user, setUser] = useState<User | null>(null); // 明确指定类型
// useRef 类型定义
const inputRef = useRef<HTMLInputElement>(null);
// 自定义 Hook 类型定义
function useApi<T>(url: string): {
data: T | null;
loading: boolean;
error: string | null;
} {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// ... 实现逻辑
return { data, loading, error };
}
配置优化
tsconfig.json 最佳配置
{
"compilerOptions": {
"target": "ES2020",
"lib": ["DOM", "DOM.Iterable", "ES2020"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@/components/*": ["src/components/*"],
"@/utils/*": ["src/utils/*"]
}
},
"include": ["src"],
"exclude": ["node_modules"]
}
错误处理
类型安全的错误处理
// 定义错误类型
class ApiError extends Error {
constructor(message: string, public status: number, public code: string) {
super(message);
this.name = "ApiError";
}
}
// 类型安全的 Result 模式
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
async function fetchUser(id: number): Promise<Result<User, ApiError>> {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
return {
success: false,
error: new ApiError(
"Failed to fetch user",
response.status,
"FETCH_ERROR"
),
};
}
const user = await response.json();
return { success: true, data: user };
} catch (error) {
return {
success: false,
error: new ApiError("Network error", 0, "NETWORK_ERROR"),
};
}
}
性能优化
类型导入优化
// ✅ 推荐:使用 type-only 导入
import type { User, ApiResponse } from "./types";
import { fetchUser } from "./api";
// ✅ 推荐:分离类型定义文件
// types/user.ts
export interface User {
id: number;
name: string;
email: string;
}
// types/api.ts
export interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
避免 any 类型
// ❌ 避免使用 any
function processData(data: any): any {
return data.someProperty;
}
// ✅ 推荐:使用泛型或 unknown
function processData<T>(data: T): unknown {
return (data as any).someProperty; // 明确标注类型断言
}
// ✅ 更好的方案:定义具体类型
interface DataWithProperty {
someProperty: unknown;
}
function processData(data: DataWithProperty): unknown {
return data.someProperty;
}
测试中的 TypeScript
类型安全的测试
// 测试工具函数的类型定义
function createMockUser(overrides: Partial<User> = {}): User {
return {
id: 1,
name: "Test User",
email: "test@example.com",
createdAt: new Date(),
...overrides,
};
}
// 类型安全的测试断言
function assertIsUser(value: unknown): asserts value is User {
if (typeof value !== "object" || value === null) {
throw new Error("Expected object");
}
const obj = value as Record<string, unknown>;
if (typeof obj.id !== "number") {
throw new Error("Expected id to be number");
}
if (typeof obj.name !== "string") {
throw new Error("Expected name to be string");
}
}
常见问题和解决方案
处理第三方库类型
// 为没有类型定义的库创建声明文件
// types/my-library.d.ts
declare module "my-library" {
export function doSomething(param: string): number;
export interface LibraryConfig {
apiKey: string;
timeout: number;
}
}
环境变量类型定义
// types/env.d.ts
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: "development" | "production" | "test";
API_URL: string;
DATABASE_URL: string;
}
}
总结
TypeScript 最佳实践包括:
- 严格的类型定义 - 使用接口和类型别名明确定义数据结构
- 合理使用泛型 - 提高代码复用性和类型安全性
- 避免 any 类型 - 保持类型安全
- 优化配置 - 使用严格模式和合适的编译选项
- 类型安全的错误处理 - 使用 Result 模式等技巧
- 性能优化 - 使用 type-only 导入和合理的类型结构
通过遵循这些最佳实践,我们可以充分发挥 TypeScript 的优势,构建更加健壮和可维护的应用程序。
评论
欢迎在下方留言讨论