AstroResearch/dashboard/前端开发规范-React篇.md

18 KiB
Raw Blame History

前端开发规范-React篇

React 开发实践指南

V1.0 | 2026年5月 \

一、概述与总则

本文档旨在统一前端开发团队的技术实践标准,确保代码质量、可维护性和团队协作效率。规范覆盖 React 技术栈的核心开发场景,同时涵盖 HTML/CSS、JavaScript/TypeScript 等基础层面的通用准则。

1.1 适用范围

本规范适用于所有使用 React 技术栈的前端项目,包括但不限于:

  1. 使用 React 18+ 的 Web 应用项目

  2. 基于 Next.js、Remix 等元框架的服务端渲染项目

  3. 使用 React Native 的移动端跨平台项目(部分适用)

1.2 规范层级

规范条目按强制程度分为三个层级,开发者应根据项目实际情况合理遵循:

层级 标识 说明
必须Must [M] 所有项目必须严格遵守Code Review 中必检项
推荐Should [S] 强烈建议遵循,特殊场景经评估后可调整
可选May [O] 根据项目实际情况选择性采纳

1.3 技术栈版本要求

新项目应优先采用以下技术栈版本,已有项目应在迭代周期内逐步升级:

技术项 推荐版本 说明
React 18.x / 19.x 使用最新稳定版
TypeScript 5.5+ strict 模式启用
Vite 6.x 构建工具首选
Next.js 15.x SSR/SSG 场景
Tailwind CSS 4.x 原子化 CSS 方案
ESLint 9.x Flat Config 格式

二、React 开发规范

React 是本规范的核心关注领域。本章从组件设计、Hooks 使用、状态管理、TypeScript 类型约束和性能优化五个维度,系统性地定义 React 开发的最佳实践。

2.1 组件设计规范

2.1.1 组件分类与组织

React 组件应按职责明确划分为以下类别,并在项目目录中保持清晰的组织结构:

组件类型 存放路径 职责说明
Page 组件 app/ 或 pages/ 路由级别的页面组件,负责数据获取和页面级布局
Layout 组件 components/layout/ 页面布局框架,如 Header、Sidebar、Footer
UI 组件 components/ui/ 基础 UI 元素Button、Input、Modal 等纯展示组件
Feature 组件 features/*/components/ 业务功能组件,与特定功能域紧耦合
HOC / 工具 components/hoc/ 高阶组件和渲染工具render props

2.1.2 函数组件优先

自 React 16.8 引入 Hooks 以来,函数组件已成为官方推荐的标准写法。所有新开发组件必须使用函数组件,类组件仅在维护遗留代码时允许存在。

// 推荐:函数组件 + Hooks
import { useState, useCallback } from 'react';

interface UserCardProps {
  user: User;
  onSelect: (id: string) => void;
}

export function UserCard({ user, onSelect }: UserCardProps) {
  const [expanded, setExpanded] = useState(false);

  const handleClick = useCallback(() => {
    onSelect(user.id);
    setExpanded(prev => !prev);
  }, [onSelect, user.id]);

  return (
    <Card onClick={handleClick}>
      <Avatar src={user.avatar} />
      <UserName>{user.name}</UserName>
      {expanded && <UserDetail user={user} />}
    </Card>
  );
}

2.1.3 Props 设计原则

组件的 Props 接口设计直接影响组件的可复用性和可维护性。遵循以下原则:

  1. 单一职责:每个组件只接收其渲染所需的最小数据集合,避免传递冗余数据

  2. 显式接口:使用 TypeScript interface 定义 Props禁止隐式 any 类型

  3. 默认值策略:对可选 Props 提供合理的默认值,或使用解构赋值简化处理

  4. 事件命名:自定义事件处理器以 on 为前缀(如 onSelect、onValueChange遵循 React 原生事件命名惯例

  5. 避免过度透传:不要简单地将父组件的 Props 全部展开传递给子组件,应显式声明所需属性

2.1.4 组件文件结构

每个组件应按以下结构组织,确保关注点分离和可测试性:

// components/UserCard/index.tsx
export { UserCard } from './UserCard';
export type { UserCardProps } from './types';

// components/UserCard/UserCard.tsx
import { useState } from 'react';
import type { UserCardProps } from './types';
import { useUserCard } from './useUserCard';
import * as S from './styles';

export function UserCard({ user, onSelect }: UserCardProps) {
  const { expanded, handleClick } = useUserCard(user, onSelect);
  return (
    <S.Card onClick={handleClick}>...</S.Card>
  );
}

// components/UserCard/types.ts
export interface UserCardProps {
  user: User;
  onSelect: (id: string) => void;
}

// components/UserCard/useUserCard.ts
export function useUserCard(user: User, onSelect: (id: string) => void) {
  // 业务逻辑抽离到自定义 Hook
}

// components/UserCard/styles.ts (styled-components / CSS Modules)

2.2 Hooks 使用规范

2.2.1 Hooks 基础规则

Hooks 是 React 16.8 引入的革命性特性,必须严格遵循以下使用规则,否则可能导致不可预期的行为:

  1. 只在最顶层调用 Hooks不要在循环、条件判断或嵌套函数中调用 Hooks

  2. 只在 React 函数中调用 Hooks在函数组件或自定义 Hook 中调用,不要在普通 JavaScript 函数中调用

  3. 以 use 开头命名:自定义 Hook 必须以 use 开头命名,以便 ESLint 插件识别

  4. 依赖数组诚实原则useEffect、useMemo、useCallback 的依赖数组必须完整列出所有依赖项

2.2.2 useEffect 最佳实践

useEffect 是最常用的 Hook 之一,也是最容易滥用的。遵循以下实践:

// 推荐:单一职责的 Effect
useEffect(() => {
  const controller = new AbortController();
  fetchUser(userId, { signal: controller.signal })
    .then(setUser)
    .catch(setError);
  return () => controller.abort();
}, [userId]); // 依赖数组必须完整

// 推荐:逻辑拆分到独立 Effect
useEffect(() => {
  // 数据获取逻辑
}, [params]);

useEffect(() => {
  // DOM 操作或订阅逻辑
  return () => { /* 清理逻辑 */ };
}, []);

// 禁止:缺失依赖项
useEffect(() => {
  fetchData(page); // page 未在依赖数组中!
}, []); // eslint-disable-line 是临时方案,应尽快修复

2.2.3 useMemo 与 useCallback

性能优化 Hooks 应在有明确性能问题时使用,避免过早优化。遵循以下准则:

  1. useMemo用于缓存昂贵的计算结果仅在计算成本显著高于缓存开销时使用

  2. useCallback用于缓存事件处理函数主要配合 React.memo 使用,避免子组件不必要的重渲染

  3. 避免滥用:简单的计算和事件处理不需要 memoizationReact 的渲染性能通常优于预期

  4. 依赖数组完整性:与 useEffect 同样,必须确保依赖数组的完整性

// 推荐:复杂数据转换使用 useMemo
const filteredUsers = useMemo(() => {
  return users
    .filter(u => u.active)
    .sort((a, b) => b.score - a.score)
    .slice(0, 100);
}, [users]);

// 推荐:配合 React.memo 使用 useCallback
const handleSubmit = useCallback((values: FormData) => {
  api.submit(values).then(onSuccess);
}, [onSuccess]);

// 禁止:对简单值使用 useMemo
const fullName = useMemo(
  () => `$${firstName} $${lastName}`,
  [firstName, lastName] // 字符串拼接成本极低,无需缓存
);

2.3 状态管理规范

2.3.1 状态管理策略

React 应用的状态管理应按状态的作用域和复杂度选择适当的方案,避免过度工程化:

状态类型 管理方案 适用场景
本地 UI 状态 useState 组件内部的临时状态,如表单输入、展开/收起
派生状态 useMemo / 计算 可从已有状态计算得出的值
共享状态 Context / Props 跨 2-3 层组件传递的状态
全局状态 Zustand / Jotai 应用级共享状态,如用户信息、主题设置
服务端状态 TanStack Query 服务器数据缓存和同步
表单状态 React Hook Form 复杂表单的状态和验证管理

2.3.2 Context 使用规范

React Context 适用于跨组件层级的数据传递,但不当使用会导致性能问题:

  1. 拆分 Context将高频变化和低频变化的状态拆分到独立的 Context避免不必要的重渲染

  2. 避免过度使用:仅在真正需要跨多级组件传递数据时使用,简单的父子组件通信仍应通过 Props

  3. 结合 useReducer对于复杂状态逻辑Context 配合 useReducer 可以实现轻量级的 Redux-like 方案

// 推荐:拆分 Context 避免重渲染
const ThemeContext = createContext<Theme>('light');
const UserContext = createContext<User | null>(null);

// ThemeProvider 更新时,只消费 ThemeContext 的组件重渲染
// UserProvider 更新时,只消费 UserContext 的组件重渲染

// 推荐Context + useReducer 组合
type Action = { type: 'increment' } | { type: 'decrement' };
const CounterContext = createContext<{
  state: number;
  dispatch: React.Dispatch<Action>;
} | null>(null);

2.3.3 外部状态管理Zustand/Jotai

对于中大型企业级应用,推荐使用轻量级的原子化状态管理方案,如 Zustand 或 Jotai

  1. Zustand适合模块化的 Store 架构API 极简,无 Provider 包裹问题

  2. Jotai适合原子化的细粒度状态管理状态依赖自动追踪

  3. 避免 Redux 过度使用:仅在需要 Redux DevTools 时间旅行调试、复杂中间件链时考虑 Redux Toolkit

2.4 TypeScript 类型规范

2.4.1 组件 Props 类型

所有组件 Props 必须使用 TypeScript 接口显式定义,禁止使用 any 类型绕过类型检查:

// 推荐:显式 Props 接口
interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  loading?: boolean;
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
  children: React.ReactNode;
}

export function Button({
  variant = 'primary',
  size = 'md',
  disabled = false,
  loading = false,
  onClick,
  children,
}: ButtonProps) {
  // 实现...
}

// 禁止:隐式 any 或缺少类型
// function Button(props) { // 错误props 为 any
//   return <button>{props.label}</button>;
// }

2.4.2 泛型组件

对于数据展示类组件,使用泛型实现类型安全的通用组件:

// 推荐:泛型表格组件
interface DataTableProps<T> {
  data: T[];
  columns: ColumnDef<T>[];
  keyExtractor: (item: T) => string;
  onRowClick?: (item: T) => void;
}

export function DataTable<T>({
  data, columns, keyExtractor, onRowClick,
}: DataTableProps<T>) {
  return (
    <table>
      <tbody>
        {data.map(item => (
          <tr key={keyExtractor(item)}
              onClick={() => onRowClick?.(item)}>
            {columns.map(col => (
              <td key={col.key}>{col.render(item)}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

2.4.3 事件类型

React 事件处理函数应使用 React 提供的泛型事件类型,而非原生 DOM 事件类型:

事件类型 React 类型
点击事件 React.MouseEvent<HTMLButtonElement>
输入事件 React.ChangeEvent<HTMLInputElement>
表单提交 React.FormEvent<HTMLFormElement>
键盘事件 React.KeyboardEvent<HTMLInputElement>
拖拽事件 React.DragEvent<HTMLDivElement>
触摸事件 React.TouchEvent<HTMLDivElement>
通用事件 React.SyntheticEvent

2.5 性能优化规范

2.5.1 渲染优化

React 的渲染优化应从以下维度系统化地进行:

  1. React.memo对纯展示组件使用 React.memo 进行浅比较优化,避免不必要的重渲染

  2. useMemo / useCallback对昂贵的计算和传递给子组件的回调进行缓存

  3. 虚拟列表:长列表使用 react-window 或 react-virtualized 实现虚拟滚动

  4. 代码分割:使用 React.lazy + Suspense 实现路由级别和组件级别的懒加载

// 推荐React.memo + 自定义比较函数
export const UserList = React.memo(function UserList({
  users,
  onSelect,
}: UserListProps) {
  return (
    <ul>
      {users.map(user => (
        <UserItem key={user.id} user={user} onSelect={onSelect} />
      ))}
    </ul>
  );
}, (prev, next) => prev.users === next.users);

// 推荐React.lazy 代码分割
const Dashboard = React.lazy(() => import('./Dashboard'));
const Settings = React.lazy(() => import('./Settings'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

2.5.2 状态更新优化

状态更新方式直接影响渲染性能,应遵循以下最佳实践:

  1. 批量更新React 18 自动批处理所有状态更新,无需手动合并

  2. 不可变数据:始终使用不可变更新模式,配合 useMemo/React.memo 进行引用比较

  3. 状态拆分:将独立变化的状态拆分为多个 useState避免不必要的联合更新

  4. 派生状态:优先使用 useMemo 计算派生状态,避免在状态中存储可计算的值

三、HTML/CSS 规范

HTML 和 CSS 是前端开发的基础,良好的标记和样式实践是构建可维护应用的前提。

3.1 HTML 语义化

语义化的 HTML 不仅有利于可访问性A11y也有助于 SEO 和代码的可读性:

  1. 使用恰当的语义化标签header、nav、main、article、section、aside、footer

  2. 表单元素必须关联 label使用 aria-label 或 aria-labelledby 补充描述

  3. 图片必须提供有意义的 alt 文本,装饰性图片使用 alt=""

  4. 遵循标题层级顺序h1 → h2 → h3不要跳级使用

3.2 CSS 架构

推荐采用CSS Modules避免全局命名空间污染

// 推荐CSS Modules
import styles from './Button.module.css';

export function Button({ children }) {
  return <button className={styles.button}>{children}</button>;
}

/* Button.module.css */
.button {
  padding: 8px 16px;
  border-radius: 4px;
  background: var(--color-primary);
  color: white;
}

.button:hover {
  background: var(--color-primary-dark);
}

3.3 响应式设计

所有界面必须适配至少三种断点,采用移动优先的设计策略:

断点名 尺寸范围 适配策略
Mobile < 768px 单列布局、触摸友好的交互、简化导航
Tablet 768px - 1024px 双列布局、侧边栏可收起、适配触控
Desktop > 1024px 完整多列布局、 hover 交互、固定侧边栏

四、JavaScript/TypeScript 通用规范

除 React 特定规范外,团队应遵循以下 JavaScript/TypeScript 通用编码规范。

4.1 命名规范

  1. 文件名PascalCase 用于组件文件UserCard.tsxcamelCase 用于工具文件formatDate.ts

  2. 组件名PascalCase与文件名保持一致

  3. Hook 名:以 use 开头,后跟 PascalCaseuseUserData

  4. 常量UPPER_SNAKE_CASEMAX_RETRY_COUNT

  5. 布尔变量:使用 is、has、should 等前缀isLoading、hasError

4.2 代码风格

统一使用 ESLint + Prettier 进行代码格式化和质量检查,配置文件纳入版本控制:

// .eslintrc.cjs
module.exports = {
  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended',
    'plugin:react-hooks/recommended',
  ],
  rules: {
    '@typescript-eslint/no-explicit-any': 'error',
    '@typescript-eslint/explicit-function-return-type': 'warn',
    'react-hooks/exhaustive-deps': 'error',
  },
};

// .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "all"
}

4.3 类型安全

  1. strict 模式TypeScript 配置必须启用 strict: true

  2. 禁止 any原则上禁止使用 any 类型,必要时应使用 unknown 并配合类型收窄

  3. 返回值类型:公共函数应显式声明返回值类型,利用类型推断的边界情况除外

  4. 类型导出:组件 Props 接口应随组件一起导出,便于复用

五、工程化与项目结构

良好的项目结构和工程化配置是团队协作的基石。

5.1 目录结构

推荐采用以下目录组织方式Feature-based 结构优先:

src/
├── app/                    # 路由页面Next.js / React Router
   ├── layout.tsx
   ├── page.tsx
   └── dashboard/
       └── page.tsx
├── components/             # 共享组件
   ├── ui/                 # 基础 UI 组件Button, Input, Modal
   └── layout/             # 布局组件Header, Sidebar, Footer
├── features/               # 功能模块
   └── auth/               # 认证功能
       ├── api/            # API 请求
       ├── components/     # 功能组件
       ├── hooks/          # 功能 Hooks
       ├── stores/         # 状态管理
       └── types.ts        # 功能类型
├── hooks/                  # 全局共享 Hooks
├── lib/                    # 工具库和配置
   ├── api.ts              # Axios 实例配置
   └── utils.ts            # 通用工具函数
├── types/                  # 全局类型定义
└── styles/                 # 全局样式和主题配置

5.2 开发工作流

  1. Git 分支策略:采用 Git Flow 或 Trunk-based 开发,功能分支命名格式 feature/描述 或 fix/描述

  2. 代码审查:所有代码变更必须通过 Pull Request 审查,至少 1 人 approving 后方可合并

  3. 提交规范:遵循 Conventional Commits 规范feat:、fix:、docs:、refactor:、test: 等前缀)

  4. CI/CD集成自动化测试、代码质量检查ESLint、TypeScript 编译检查)到 CI 流水线

六、性能优化与最佳实践

性能优化是前端开发的重要环节,应贯穿整个开发周期。

6.1 加载性能

  1. 资源压缩:启用 Gzip/Brotli 压缩,图片使用 WebP/AVIF 格式

  2. 懒加载:路由、图片、非首屏组件均使用懒加载策略

  3. 预加载:对关键资源使用 rel=preload对后续路由使用 rel=prefetch

  4. Bundle 分析:定期使用 @next/bundle-analyzer 或 webpack-bundle-analyzer 分析打包体积

6.2 运行时性能

  1. 避免频繁的状态更新使用防抖debounce和节流throttle控制高频事件

  2. Web Workers将复杂计算 offload 到 Web Worker避免阻塞主线程

  3. 内存管理:及时清理定时器、事件监听器和订阅,防止内存泄漏

  4. 虚拟化:长列表使用虚拟滚动,大数据集使用分页或虚拟表格