fischerX/docs/development/best-practices.md

8.7 KiB

最佳实践

> 文档版本: v1.0.0 > 创建日期: 2026-05-25 > 适用范围: FischerX 项目开发人员

目录


前端最佳实践

组件设计

单一职责原则

每个组件应该只做一件事,保持组件保持组件应该组件应该只做一件事

// 好的做法
function UserList({ users }: { users: User[] }) {
  return (
    <div className="user-list">
      {users.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}

function UserCard({ user }: { user: User }) {
  return (
    <div className="user-card">
      <UserAvatar user={user} />
      <UserInfo user={user} />
    </div>
  );
}

// 避免 - 组件太大太组件太组件太大了
function UserPage({ users }: { users: User[] }) {
  return (
    <div className="user-page">
      {users.map(user => (
        <div key={user.id} className="user-card">
          <img src={user.avatar} alt={user.name} />
          <div>
            <h3>{user.name}</h3>
            <p>{user.email}</p>
          </div>
        </div>
      ))}
    </div>
  );
}

Props 解构与组合

使用组合而不是继承,使用组合而不是继承

// 好的做法
function Button({
  children, variant = 'primary', className, ...props }: ButtonProps) {
  return (
    <button
      className={cn(buttonVariants({ variant }), className)}
      {...props}
    >
      {children}
    </button>
  );
}

// 使用
<Button variant="secondary" onClick={handleClick} disabled={isLoading}>
  <Icon />
  Submit
</Button>

状态管理

使用 React Query 进行服务器状态

// hooks/useUsers.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { userApi } from '@/lib/api';

export function useUsers() {
  return useQuery({
    queryKey: ['users'],
    queryFn: userApi.getAll,
  });
}

export function useCreateUser() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: userApi.create,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
}

使用 Zustand 进行客户端状态

// stores/useAuthStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface AuthState {
  user: User | null;
  token: string | null;
  setUser: (user: User | null) => void;
  setToken: (token: string | null) => void;
  logout: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      user: null,
      token: null,
      setUser: (user) => set({ user }),
      setToken: (token) => set({ token }),
      logout: () => set({ user: null, token: null }),
    }),
    {
      name: 'auth-storage',
    }
  )
);

性能优化

组件懒加载

// 使用动态导入
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
  loading: () => <div>Loading...</div>,
  ssr: false,
});

// 在需要时再加载
function SomePage() {
  const [showHeavy, setShowHeavy] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowHeavy(true)}>Show</button>
      {showHeavy && <HeavyComponent />}
    </div>
  );
}

使用 useMemo 和 useCallback

// 只在 dependencies 变化时重新计算
const filteredUsers = useMemo(() => {
  return users.filter(user => user.active);
}, [users]);

// 只在 dependencies 变化时重新创建函数
const handleUserClick = useCallback((user: User) => {
  navigate(`/users/${user.id}`);
}, [navigate]);

后端最佳实践

API 设计

RESTful 规范

// 好的做法
GET    /users              # 获取用户列表
GET    /users/:id          # 获取单个用户
POST   /users              # 创建用户
PUT    /users/:id          # 更新用户
DELETE /users/:id          # 删除用户

GET    /users/:id/orders # 获取用户订单

统一响应格式

// 成功响应
{
  "success": true,
  "data": { ... },
  "message": "Operation successful"
}

// 错误响应
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "User not found",
    "details": []
  }
}

数据库设计

使用 Prisma 迁移

// schema.prisma
model User {
  id        String   @id @default(cuid())
  name      String
  email     String   @unique
  password  String
  role      Role     @default(USER)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  
  @@index([email])
}

enum Role {
  USER
  ADMIN
}

数据库查询优化

// 使用 select 只查询需要的字段
async function getUsers() {
  return this.prisma.user.findMany({
    select: {
      id: true,
      name: true,
      email: true,
    },
  });
}

// 使用 include 加载关联数据
async function getUserWithOrders(id: string) {
  return this.prisma.user.findUnique({
    where: { id },
    include: { orders: true },
  });
}

缓存策略

// 使用 Redis 缓存
@Injectable()
export class UserService {
  constructor(
    private prisma: PrismaService,
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
  ) {}

  async findOne(id: string) {
    const cacheKey = `user:${id}`;
    const cached = await this.cacheManager.get(cacheKey);
    
    if (cached) {
      return cached;
    }
    
    const user = await this.prisma.user.findUnique({ where: { id } });
    await this.cacheManager.set(cacheKey, user, 300); // 5 分钟
    
    return user;
  }
}

共享包开发

包的设计原则

高内聚低耦合

// packages/utils/src/string.ts
export function formatDate(date: Date): string {
  return new Intl.DateTimeFormat('zh-CN').format(date);
}

export function truncate(str: string, length: number): string {
  return str.length > length ? `${str.slice(0, length)}...` : str;
}

export function capitalize(str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

明确的导出

// packages/utils/src/index.ts
export * from './string';
export * from './date';
export * from './validation';

类型安全

// packages/types/src/user.ts
export interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

export type CreateUserInput = Omit<User, 'id'>;
export type UpdateUserInput = Partial<CreateUserInput>;

测试最佳实践

单元测试

使用 Vitest 进行前端测试

// components/__tests__/UserCard.test.tsx
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { UserCard } from '../UserCard';

describe('UserCard', () => {
  const user = {
    id: '1',
    name: 'John Doe',
    email: 'john@example.com',
  };

  it('renders user information', () => {
    render(<UserCard user={user} />);
    
    expect(screen.getByText('John Doe')).toBeInTheDocument();
    expect(screen.getByText('john@example.com')).toBeInTheDocument();
  });
});

使用 Jest 进行后端测试

// services/api/src/user/user.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';

describe('UserService', () => {
  let service: UserService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [UserService],
    }).compile();

    service = module.get<UserService>(UserService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });
});

测试原则

  • AAA 模式: Arrange, Act, Assert
  • **描述性的测试名称
  • **测试应该独立
  • **测试行为而不是实现
describe('UserService', () => {
  it('should create a new user', async () => {
    // Arrange
    const createUserDto = {
      name: 'Test',
      email: 'test@example.com',
      password: 'password123',
    };
    
    // Act
    const result = await service.create(createUserDto);
    
    // Assert
    expect(result.name).toEqual('Test');
  });
});

下一步


> 文档维护: 本文档由开发团队维护,如有问题或建议请提交 Issue。 > 最后更新: 2026-05-25