TypeScript 最佳实践:构建类型安全的应用

2024年12月15日作者

分享 TypeScript 开发中的最佳实践,帮助构建类型安全、可维护的应用程序

TypeScriptJavaScript前端开发类型安全最佳实践

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 最佳实践包括:

  1. 严格的类型定义 - 使用接口和类型别名明确定义数据结构
  2. 合理使用泛型 - 提高代码复用性和类型安全性
  3. 避免 any 类型 - 保持类型安全
  4. 优化配置 - 使用严格模式和合适的编译选项
  5. 类型安全的错误处理 - 使用 Result 模式等技巧
  6. 性能优化 - 使用 type-only 导入和合理的类型结构

通过遵循这些最佳实践,我们可以充分发挥 TypeScript 的优势,构建更加健壮和可维护的应用程序。

评论

欢迎在下方留言讨论

加载评论中...