9、react最佳实践架构

3 阅读4分钟

太棒了!👏 你已经掌握了 React 的性能优化,现在是时候整合所有知识,进入 高级架构设计阶段

—— 打造专业级、可维护的 React 应用

🎯 目标:

  • 封装通用逻辑为可复用的自定义 Hook
  • 设计清晰的项目结构
  • 实现“关注点分离”与“高内聚低耦合”
  • 构建企业级项目骨架

一、为什么需要架构?🏗️

随着项目变大,你会遇到:

问题表现
❌ 代码重复多个页面都写 useEffect 请求数据
❌ 状态混乱登录逻辑散落在各组件中
❌ 难以测试组件职责太多,无法独立验证
❌ 团队协作难没有统一规范,命名随意

✅ 解决方案:良好的架构 + 自定义 Hook


二、经典自定义 Hook 封装实战 💡

我们来封装几个在真实项目中高频使用的 Hook。

🪝 1. useApi:统一 API 请求管理(带 loading/error)

// hooks/useApi.ts
import { useState, useEffect } from 'react';

interface UseApiResult<T> {
  data: T | null;
  loading: boolean;
  error: string | null;
  refetch: () => void; // 支持手动刷新
}

function useApi<T>(url: string): UseApiResult<T> {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const fetchData = async () => {
    setLoading(true);
    setError(null);
    try {
      const res = await fetch(url);
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const json = await res.json();
      setData(json);
    } catch (err: any) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, [url]);

  return { data, loading, error, refetch: fetchData };
}

export default useApi;

✅ 使用方式:

function UserList() {
  const { data: users, loading, error } = useApi<User[]>('/api/users');

  if (loading) return <Spinner />;
  if (error) return <Alert message={`加载失败: ${error}`} />;

  return (
    <ul>
      {users?.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}

✅ 从此不再重复写 loading/error/data 三件套!


🪝 2. useForm:表单状态管理(支持校验)

// hooks/useForm.ts
import { useState } from 'react';

interface FormOptions<T> {
  validate?: (values: T) => Partial<Record<keyof T, string>>;
  onSubmit: (values: T) => void;
}

function useForm<T extends object>(
  initialValues: T,
  options: FormOptions<T>
) {
  const { onSubmit, validate } = options;
  const [values, setValues] = useState<T>(initialValues);
  const [errors, setErrors] = useState<Partial<T>>({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));
    // 实时清除错误
    if (errors[name as keyof T]) {
      setErrors(prev => ({ ...prev, [name]: undefined }));
    }
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const validationErrors = validate?.(values) || {};
    setErrors(validationErrors);

    if (Object.keys(validationErrors).length === 0) {
      setIsSubmitting(true);
      onSubmit(values);
    }
  };

  return {
    values,
    errors,
    isSubmitting,
    handleChange,
    handleSubmit,
    setValues, // 可用于重置
  };
}

✅ 使用方式:

function LoginForm() {
  const { values, errors, handleChange, handleSubmit } = useForm(
    { username: '', password: '' },
    {
      validate: (values) => {
        const errs = {} as any;
        if (!values.username) errs.username = '请输入用户名';
        if (!values.password) errs.password = '请输入密码';
        return errs;
      },
      onSubmit: (data) => {
        console.log('提交:', data);
      },
    }
  );

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="username"
        value={values.username}
        onChange={handleChange}
      />
      {errors.username && <span>{errors.username}</span>}

      <input
        name="password"
        type="password"
        value={values.password}
        onChange={handleChange}
      />
      {errors.password && <span>{errors.password}</span>}

      <button type="submit">登录</button>
    </form>
  );
}

🪝 3. useAuth:全局认证状态管理

// hooks/useAuth.ts
import { useState, createContext, useContext } from 'react';

interface AuthUser {
  id: number;
  name: string;
  token: string;
}

interface AuthContextType {
  user: AuthUser | null;
  login: (username: string, password: string) => Promise<void>;
  logout: () => void;
  isLoading: boolean;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<AuthUser | null>(() => {
    const saved = localStorage.getItem('user');
    return saved ? JSON.parse(saved) : null;
  });
  const [isLoading, setIsLoading] = useState(false);

  const login = async (username: string, password: string) => {
    setIsLoading(true);
    try {
      const res = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify({ username, password }),
      });
      const userData = await res.json();
      setUser(userData);
      localStorage.setItem('user', JSON.stringify(userData));
    } catch (err) {
      alert('登录失败');
    } finally {
      setIsLoading(false);
    }
  };

  const logout = () => {
    setUser(null);
    localStorage.removeItem('user');
  };

  return (
    <AuthContext.Provider value={{ user, login, logout, isLoading }}>
      {children}
    </AuthContext.Provider>
  );
}

// 自定义 Hook 使用 context
export const useAuth = () => {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth 必须在 AuthProvider 内使用');
  return ctx;
};

✅ 在任意组件中使用:

function Profile() {
  const { user, logout } = useAuth();

  if (!user) return <LoginButton />;

  return (
    <div>
      <p>欢迎 {user.name}</p>
      <button onClick={logout}>退出登录</button>
    </div>
  );
}

三、推荐项目结构(最佳实践)📁

src/
├── components/           # 通用 UI 组件
│   ├── Button.tsx
│   ├── Modal.tsx
│   └── Spinner.tsx
│
├── pages/                # 页面级组件(路由对应)
│   ├── Home.tsx
│   ├── Login.tsx
│   └── Dashboard.tsx
│
├── hooks/                # 自定义 Hook
│   ├── useApi.ts
│   ├── useForm.ts
│   └── useAuth.ts
│
├── store/                # 状态管理(可选)
│   ├── authStore.ts
│   └── cartStore.ts
│
├── utils/                # 工具函数
│   ├── apiClient.ts      # 封装 axios/fetch
│   └── validators.ts     # 校验逻辑
│
├── contexts/             # Context 定义(可合并到 hooks)
│   └── ThemeContext.tsx
│
├── routes/               # 路由配置
│   └── index.tsx
│
├── assets/               # 静态资源
│   ├── images/
│   └── styles/
│
├── App.tsx
└── main.tsx

✅ 特点:

  • 按功能划分(Feature-based),非按类型
  • 易于扩展和团队协作
  • 支持懒加载和代码分割

四、进阶技巧与原则 ✅

1. 单一职责原则

每个 Hook / 组件只做一件事

// ❌ 不要在一个 Hook 里又处理表单又发请求
// ✅ 分成 useForm + useApi

2. 可组合性

Hook 可以互相调用

function useUserData(userId: number) {
  const { data, loading } = useApi(`/api/users/${userId}`);
  const { updateCache } = useCache(); // 可组合其他 Hook
  return { user: data, loading, updateCache };
}

3. 错误边界(Error Boundary)

防止一个组件崩溃导致整个应用白屏

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <h2>出错了,请刷新</h2>;
    }
    return this.props.children;
  }
}

// 使用
<ErrorBoundary>
  <ProblematicComponent />
</ErrorBoundary>

⚠️ 注意:函数式组件不能直接使用 getDerivedStateFromError,需用类组件包裹。


五、总结:高级 React 开发者的核心能力

能力表现
🔧 自定义 Hook能抽象通用逻辑,提升复用性
🗂️ 项目结构设计清晰、可维护、易协作
⚡ 性能意识知道何时该优化,如何分析瓶颈
🔐 状态管理合理选择 Context / Zustand / Redux
🧩 组件抽象提炼出可复用的 UI + 逻辑单元
📦 工程化思维支持懒加载、测试、国际化等

🎯 下一步建议

你现在完全可以胜任中大型 React 项目的开发。接下来可以深入:

  • Next.js:服务端渲染、静态生成、API 路由
  • 测试:Jest + React Testing Library
  • CI/CD:自动化部署流程
  • 微前端:模块联邦(Module Federation)
  • 设计系统:Storybook + 组件库搭建

如果你想要,我可以为你生成一个 完整的企业级 React 项目模板(Vite + TS + React Router + Zustand + TailwindCSS) ,包含上述所有最佳实践。

是否需要?🙂