16 KiB
16 KiB
React 代码编写规范大全
编写 React 代码不仅仅是让功能跑起来,更重要的是保证代码的可读性、可维护性和团队协作效率。一套良好的编码规范是达成这些目标的基石。
本文将从基础规范、组件设计、状态管理、样式处理、性能优化和项目架构六个层次,由浅入深地详解 React 的代码编写规范。
📋 目录
一、核心六层规范架构
React 代码规范的六个核心层次,构成了一个完整、健壮的应用开发体系:
React 代码规范六层体系
├── L1: 基础与核心规范 (命名、文件组织、JSX、PropTypes)
│ └── 目标: 代码一致性与可读性
├── L2: 组件设计模式 (组件拆分、组合、解耦)
│ └── 目标: 可复用与可维护性
├── L3: 状态管理规范 (State原则、Reducer、Context、Redux)
│ └── 目标: 数据流清晰与可预测
├── L4: 样式与CSS策略 (CSS Modules、Styled-Components、方案选型)
│ └── 目标: 样式可控与避免冲突
├── L5: 性能优化指南 (Memo、Callback、懒加载、列表优化)
│ └── 目标: 应用流畅与用户体验
└── L6: 项目结构与架构 (目录组织、路由、配置、静态资源)
└── 目标: 项目可扩展与易于协作
二、L1 - 基础与核心规范
这是最基础也是必须遵守的规范,保证了代码的一致性和可读性。
1. 命名规范 (Naming Conventions)
组件命名
使用 PascalCase (大驼峰命名法),且名称与文件名一致。
// ✅ 正确
// UserProfile.tsx
function UserProfile() { ... }
// ❌ 错误
// userProfile.tsx
function user_profile() { ... }
function User_Profile() { ... }
属性命名
使用 camelCase (小驼峰命名法)。
// ✅ 正确
<Button onClick={handleClick} userName={userName} />
// ❌ 错误
<Button ONCLICK={handleClick} UserName={userName} />
自定义事件处理函数
以 handle 开头,后接事件名或操作名。
const handleInputChange = () => { ... };
const handleSubmit = () => { ... };
const handleDeleteUser = () => { ... };
布尔型 Props
前缀使用 is, has, should 等,使其语义更明确。
// ✅ 正确
<Button isDisabled={true} hasError={false} shouldShow={true} />
// ❌ 错误
<Button disabled={true} error={false} show={true} />
常量命名
使用 UPPER_SNAKE_CASE (全大写下划线分隔)。
const MAX_COUNT = 10;
const API_BASE_URL = "https://api.example.com";
const DEFAULT_TIMEOUT = 5000;
2. 文件组织 (File Organization)
文件命名
- 组件文件:使用 PascalCase,如
UserProfile.tsx - 工具函数:使用 camelCase,如
formatDate.ts - 常量文件:使用 UPPER_SNAKE_CASE,如
API_CONSTANTS.ts - 样式文件:使用 kebab-case 或与组件同名,如
user-profile.module.scss或UserProfile.module.scss
目录结构
src/
├── components/ # 通用组件
│ ├── Button/
│ │ ├── index.tsx
│ │ ├── Button.module.scss
│ │ └── types.ts
│ └── Modal/
├── pages/ # 页面组件
│ ├── Home/
│ └── Profile/
├── hooks/ # 自定义 Hooks
├── utils/ # 工具函数
├── store/ # 状态管理
├── api/ # API 接口
└── types/ # TypeScript 类型定义
3. JSX 编写规范
基本规则
// ✅ 正确:自闭合标签
<img src={avatar} alt="Avatar" />
<input type="text" value={value} />
// ❌ 错误
<img src={avatar} alt="Avatar"></img>
<input type="text" value={value}></input>
条件渲染
// ✅ 推荐:使用逻辑与运算符
{
isLoading && <LoadingSpinner />;
}
// ✅ 推荐:三元运算符(简单条件)
{
isLoggedIn ? <UserMenu /> : <LoginButton />;
}
// ✅ 推荐:提前返回(复杂条件)
if (!user) return <LoginPrompt />;
return <Dashboard />;
列表渲染
// ✅ 正确:使用 key
{
users.map(user => <UserItem key={user.id} user={user} />);
}
// ❌ 错误:使用索引作为 key(除非列表是静态的)
{
users.map((user, index) => <UserItem key={index} user={user} />);
}
属性展开
// ✅ 正确:使用展开运算符
const props = { name: "John", age: 30 };
<UserProfile {...props} />
// ❌ 错误:逐个传递
<UserProfile name={props.name} age={props.age} />
4. Props 类型检查
TypeScript 类型定义
// ✅ 推荐:使用 TypeScript
interface UserProfileProps {
name: string;
age: number;
email?: string;
isActive: boolean;
onUpdate: (id: number) => void;
}
const UserProfile: React.FC<UserProfileProps> = ({
name,
age,
email,
isActive,
onUpdate,
}) => {
// ...
};
默认值
// ✅ 使用默认参数
const UserProfile: React.FC<UserProfileProps> = ({
name,
age,
email = "",
isActive = false,
onUpdate,
}) => {
// ...
};
// 或使用 defaultProps(不推荐,TypeScript 中已废弃)
三、L2 - 组件设计模式
1. 组件拆分与组合 (Component Splitting & Composition)
单一职责原则
每个组件应该只做一件事。
// ❌ 错误:组件职责过多
const UserDashboard = () => {
return (
<div>
<UserProfile />
<UserSettings />
<UserMessages />
<UserNotifications />
</div>
);
};
// ✅ 正确:拆分为多个小组件
const UserDashboard = () => {
return (
<div>
<UserProfile />
<UserSettings />
<UserMessages />
<UserNotifications />
</div>
);
};
组件大小控制
- 单个组件文件不超过 300 行
- 如果超过,考虑拆分为多个子组件
- 复杂逻辑提取为自定义 Hook
// ✅ 正确:使用自定义 Hook 提取逻辑
const useUserData = (userId: number) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser(userId).then(data => {
setUser(data);
setLoading(false);
});
}, [userId]);
return { user, loading };
};
const UserProfile = ({ userId }: { userId: number }) => {
const { user, loading } = useUserData(userId);
// ...
};
2. 展示组件与容器组件 (Presentational vs Container Components)
展示组件 (Presentational Components)
- 只负责 UI 渲染
- 通过 props 接收数据和回调
- 不依赖业务逻辑
// ✅ 展示组件
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean;
}
const Button: React.FC<ButtonProps> = ({ label, onClick, disabled }) => {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
};
容器组件 (Container Components)
- 负责数据获取和状态管理
- 包含业务逻辑
- 将数据传递给展示组件
// ✅ 容器组件
const UserListContainer = () => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUsers().then(data => {
setUsers(data);
setLoading(false);
});
}, []);
if (loading) return <LoadingSpinner />;
return <UserList users={users} />;
};
四、L3 - 状态管理规范
1. State 放置原则
状态提升 (Lifting State Up)
将共享状态提升到最近的公共父组件。
// ❌ 错误:状态分散
const ChildA = () => {
const [sharedValue, setSharedValue] = useState("");
// ...
};
const ChildB = () => {
const [sharedValue, setSharedValue] = useState("");
// ...
};
// ✅ 正确:状态提升
const Parent = () => {
const [sharedValue, setSharedValue] = useState("");
return (
<>
<ChildA value={sharedValue} onChange={setSharedValue} />
<ChildB value={sharedValue} onChange={setSharedValue} />
</>
);
};
状态位置判断
- 组件内部状态:只在该组件内使用
- 共享状态:提升到公共父组件或使用全局状态管理
- 服务器状态:使用 React Query 或 SWR
2. 使用 useReducer 管理复杂状态
当状态逻辑复杂时,使用 useReducer 替代多个 useState。
// ✅ 使用 useReducer
interface State {
count: number;
step: number;
}
type Action =
| { type: "increment" }
| { type: "decrement" }
| { type: "reset" }
| { type: "setStep"; step: number };
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "increment":
return { ...state, count: state.count + state.step };
case "decrement":
return { ...state, count: state.count - state.step };
case "reset":
return { ...state, count: 0 };
case "setStep":
return { ...state, step: action.step };
default:
return state;
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });
// ...
};
3. 全局状态管理
Zustand (本项目使用)
// ✅ 使用 Zustand
import { create } from "zustand";
interface UserStore {
user: User | null;
setUser: (user: User) => void;
clearUser: () => void;
}
const useUserStore = create<UserStore>(set => ({
user: null,
setUser: user => set({ user }),
clearUser: () => set({ user: null }),
}));
// 使用
const UserProfile = () => {
const user = useUserStore(state => state.user);
const setUser = useUserStore(state => state.setUser);
// ...
};
Context API
适用于中等规模的全局状态。
// ✅ 使用 Context
const UserContext = createContext<UserContextType | null>(null);
export const UserProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [user, setUser] = useState<User | null>(null);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
五、L4 - 样式与CSS策略
1. CSS Modules (推荐)
本项目使用 CSS Modules 配合 SCSS。
// ✅ 使用 CSS Modules
import styles from "./UserProfile.module.scss";
const UserProfile = () => {
return <div className={styles.container}>...</div>;
};
// UserProfile.module.scss
.container {
padding: 16px;
background-color: #fff;
.title {
font-size: 24px;
font-weight: bold;
}
}
优势
- ✅ 样式作用域隔离,避免冲突
- ✅ 支持 TypeScript 类型检查
- ✅ 支持 SCSS 嵌套和变量
- ✅ 构建时优化,自动移除未使用的样式
2. 样式命名规范
// ✅ 推荐:BEM 命名法
.user-profile {
&__header {
// ...
}
&__body {
// ...
}
&__footer {
// ...
}
&--active {
// ...
}
}
3. 样式变量
// ✅ 使用 SCSS 变量
$primary-color: #1890ff;
$secondary-color: #52c41a;
$font-size-base: 14px;
.button {
background-color: $primary-color;
font-size: $font-size-base;
}
六、L5 - 性能优化指南
1. 避免不必要的重新渲染
使用 React.memo
// ✅ 使用 React.memo 优化子组件
const UserItem = React.memo(
({ user }: { user: User }) => {
return <div>{user.name}</div>;
},
(prevProps, nextProps) => {
// 自定义比较函数
return prevProps.user.id === nextProps.user.id;
},
);
使用 useMemo
// ✅ 使用 useMemo 缓存计算结果
const ExpensiveComponent = ({ items }: { items: Item[] }) => {
const sortedItems = useMemo(() => {
return items.sort((a, b) => a.price - b.price);
}, [items]);
return <div>{/* 使用 sortedItems */}</div>;
};
使用 useCallback
// ✅ 使用 useCallback 缓存函数
const Parent = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return <Child onClick={handleClick} />;
};
2. 代码分割与懒加载 (Lazy Loading)
// ✅ 使用 React.lazy 和 Suspense
import { lazy, Suspense } from "react";
const UserProfile = lazy(() => import("./pages/UserProfile"));
const App = () => {
return (
<Suspense fallback={<LoadingSpinner />}>
<UserProfile />
</Suspense>
);
};
3. 列表渲染优化
虚拟列表
对于长列表,使用虚拟滚动。
// ✅ 使用虚拟列表组件
import { FixedSizeList } from "react-window";
const VirtualizedList = ({ items }: { items: Item[] }) => {
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<Item item={items[index]} />
</div>
)}
</FixedSizeList>
);
};
七、L6 - 项目结构与架构
1. 功能分区目录结构 (Feature-Based Structure)
本项目采用功能分区 + 类型分区的混合结构:
src/
├── components/ # 通用组件(可复用)
│ ├── Button/
│ ├── Modal/
│ └── ...
├── pages/ # 页面组件(路由级别)
│ ├── Home/
│ ├── Profile/
│ └── ...
├── hooks/ # 自定义 Hooks
│ ├── useAuth.ts
│ └── useLocalStorage.ts
├── utils/ # 工具函数
│ ├── formatDate.ts
│ └── validate.ts
├── store/ # 状态管理(Zustand)
│ ├── module/
│ │ ├── user.ts
│ │ └── weChat.ts
│ └── index.ts
├── api/ # API 接口
│ ├── request.ts
│ └── module/
│ └── wechat.ts
├── types/ # TypeScript 类型定义
│ ├── user.ts
│ └── weChat.ts
└── router/ # 路由配置
├── index.tsx
└── module/
2. 使用绝对路径导入
本项目已配置路径别名 @ 指向 src 目录。
// ✅ 使用绝对路径
import { Button } from "@/components/Button";
import { useUserStore } from "@/store/module/user";
import { formatDate } from "@/utils/formatDate";
// ❌ 避免相对路径
import { Button } from "../../../components/Button";
3. 组件导出规范
// ✅ 推荐:使用 index.ts 统一导出
// components/Button/index.tsx
export { default } from "./Button";
export type { ButtonProps } from "./types";
// 使用
import Button from "@/components/Button";
import type { ButtonProps } from "@/components/Button";
4. 类型定义规范
// ✅ 类型定义文件
// types/user.ts
export interface User {
id: number;
name: string;
email: string;
}
export type UserRole = "admin" | "user" | "guest";
// 使用
import type { User, UserRole } from "@/types/user";
📝 总结
核心原则
- 一致性:保持代码风格和命名规范的一致性
- 可读性:代码应该易于理解和维护
- 可复用性:组件和函数应该尽可能可复用
- 性能:关注性能优化,但不要过度优化
- 类型安全:充分利用 TypeScript 的类型检查
检查清单
在提交代码前,确保:
- 组件命名符合 PascalCase
- 函数命名符合 camelCase
- 使用了 TypeScript 类型定义
- 样式使用了 CSS Modules
- 避免了不必要的重新渲染
- 使用了绝对路径导入
- 代码通过了 ESLint 检查
- 代码通过了 Prettier 格式化
参考资源
最后更新: 2025-01-XX 维护者: 开发团队