React + TypeScript 编码规范(实战落地版)|统一标准 & 高效维护
引言
在中大型 React 项目开发中,多团队协作、多场景业务复用极易导致代码标准不统一,出现"面条式代码"、"命名混乱"、"类型任意"等问题,直接提升代码维护成本、降低复用效率。本文基于前端编码规范完整版|基础语法 + 双框架 + 工程化全涵盖,针对 React + TypeScript 技术栈补充专项扩展规范,明确分级约束规则(强制/推荐/参考),结合真实业务场景示例落地,最终实现代码高一致性、高可维护性。
一、规范制定核心目标
- 统一标准:建立 React / TS 全链路编码标准,覆盖命名、编码、类型、样式等维度,提升代码可读性与复用性
- 落地保障:按约束力分级(强制/推荐/参考),通过 ESLint 规则、代码评审(CR)、构建扫描管控增量代码,避免劣化
- 持续运营:建立规范反馈与修订流程,适配业务场景迭代,平衡"规范约束"与"业务灵活性"
二、基础规范(Basic)
2.1 命名规范
2.1.1【推荐】命名语义化,优先完整单词,缩写需统一格式
命名需体现业务含义,避免无意义缩写;通用业务缩写全大写/全小写统一,禁止混合大小写。
typescript
// good(贴合电商订单业务)
const userOrderDetailInfo = {}; // 完整语义:用户订单详情信息
const AIR_WAYBILL = 'air waybill'; // 物流运单(全大写缩写)
const SOP = 'standard operating procedure'; // 标准作业程序
// bad
const ordDtlInfo = {}; // 缩写无语义,可读性差
const airWayBill = 'air waybill'; // 通用业务缩写混合大小写,不统一
2.1.2【强制】普通文件夹/文件名使用 kebab-case 命名
kebab-case (短横线连接小写)跨平台兼容性更好(Windows 不区分大小写),且符合前端文件命名通用约定;仅框架/约定文件(如 README.md、App.tsx)可例外。
- React Hooks 文件、组件文件、工具类文件均遵循此规则
- 示例:
user-order-profile.tsx、use-merchant-order-list.ts、order-utils.ts
2.2 编码规范
2.2.1【推荐】单文件代码量控制在 600 行以内
600 行内的文件更易进行代码评审、单元测试、重构;超过 1200 行需在代码注释中说明无法拆分的业务原因(新增 ESLint 规则:600 行触发 Warning,1200 行触发 Error)。
业务场景:电商订单页多状态(待付款/待发货/已完成)判断逻辑易超行,需拆分为
order-status-handler.ts、order-render-utils.ts等子文件
2.2.2【推荐】函数圈复杂度 ≤ 15
if 分支、for 循环、逻辑与(&&)等决策点会增加圈复杂度,值>20 时出错风险陡增。
- 工具:VS Code 插件 CodeMetrics(编码辅助)、ESLint 规则
complexity(强制管控) - 优化方案:高复杂度函数拆分为多个单一职责函数,如将"订单提交校验"拆分为
checkOrderPayStatus()、checkOrderStock()、checkOrderAddress()
三、React 专项规范
3.1 命名规范
3.1.1【强制】React 组件 PascalCase 命名,文件 kebab-case 命名
组件名体现业务含义,文件名将组件名转为 kebab-case,便于文件检索。
// 组件命名(电商订单场景)
MerchantOrderDetail // 商家订单详情
UserOrderProfile // 用户订单档案
GlobalNavigationBar // 全局导航栏
// 对应文件命名
merchant-order-detail.tsx
user-order-profile.tsx
global-navigation-bar.tsx
3.1.2【强制】React Hooks 以 use 开头 + 驼峰命名,文件 kebab-case 命名
use 前缀明确区分 Hooks 与普通函数,无需额外加 hooks 后缀。
// Hooks 命名(贴合订单业务)
useMerchantOrderList // 获取商家订单列表
useUserOrderInfo // 获取用户订单信息
useOrderTableData // 订单表格数据处理
// 对应文件命名
use-merchant-order-list.ts
use-user-order-info.ts
use-order-table-data.ts
3.1.3【推荐】useRef 变量名以 Ref 结尾
提升 ref 变量辨识度,避免与普通变量冲突。
typescript
// good(订单场景示例)
const orderFormInputRef = useRef<HTMLInputElement>(null); // 订单表单输入框ref
const orderListContainerRef = useRef<HTMLDivElement>(null); // 订单列表容器ref
const orderTimerRef = useRef<number>(); // 订单倒计时timer ref
// bad
const orderInput = useRef<HTMLInputElement>(null); // 无Ref后缀,易混淆
const container = useRef<HTMLDivElement>(null); // 无业务语义
3.1.4【推荐】JSX 属性名 camelCase,组件类型属性 PascalCase
属性名贴合业务场景,避免通用命名,提升可读性。
typescript
// good(订单组件示例)
<MerchantOrderDetail
userRealName="张三" // 驼峰:用户真实姓名(业务语义)
isOrderPaid={true} // 驼峰:订单是否支付(业务语义)
OrderStatusIcon={PaidIcon} // PascalCase:组件类型属性
/>
// bad
<MerchantOrderDetail
user_real_name="张三" // 下划线,不符合React约定
is_order_paid={true} // 下划线,语义不清晰
orderstatusicon={PaidIcon} // 小写,组件属性命名不规范
/>
3.1.5【推荐】内部方法 handle{Type}{Event},Props 回调 on{Type}{Event}
通过前缀明确区分"内部处理函数"与"外部回调函数"。
typescript
// 订单确认组件示例
interface OrderConfirmProps {
orderNo: string; // 订单编号(业务属性)
onOrderConfirm: () => void; // 外部回调:订单确认
}
const OrderConfirm: React.FC<OrderConfirmProps> = ({ orderNo, onOrderConfirm }) => {
// 内部处理函数:handle前缀 + 业务语义
const handleOrderSubmitClick = () => {
console.log(`订单 ${orderNo} 提交`);
onOrderConfirm(); // 调用外部传入的回调
};
return (
<div className="order-confirm">
<p>确认提交订单:{orderNo}</p>
<button onClick={handleOrderSubmitClick}>确认提交</button>
</div>
);
};
// 父组件使用
const OrderPage: React.FC = () => {
const handleOrderConfirm = () => {
alert('订单确认成功!');
};
return <OrderConfirm orderNo="ORD20250307001" onOrderConfirm={handleOrderConfirm} />;
};
3.1.6【推荐】自渲染函数以 render{Type} 命名
通过 render 前缀快速识别"仅负责渲染的函数",拆分复杂 JSX 逻辑。
typescript
// 订单列表组件示例
const MerchantOrderList: React.FC = () => {
// 模拟订单数据(贴合业务)
const orderList = [
{ id: 'ORD001', status: 'paid', amount: 199.9 },
{ id: 'ORD002', status: 'unpaid', amount: 299.9 }
];
// 渲染函数:render + 业务类型
const renderOrderItemList = () => (
<ul className="order-item-list">
{orderList.map(item => (
<li key={item.id} className={`order-item ${item.status}`}>
订单号:{item.id} | 金额:{item.amount} | 状态:{item.status === 'paid' ? '已支付' : '未支付'}
</li>
))}
</ul>
);
const renderOrderPageHeader = () => (
<header className="order-page-header">
<h2>商家订单列表</h2>
<button>导出订单</button>
</header>
);
return (
<div className="merchant-order-list">
{renderOrderPageHeader()}
{renderOrderItemList()}
</div>
);
};
3.1.7【强制】React Router path 采用 kebab-case 命名
符合 Google 网址最佳实践,提升 URL 可读性与搜索引擎友好性。
typescript
// good(商家自提点管理场景)
const routes = {
path: 'merchant-pickup-point-management', // kebab-case:业务语义+层级清晰
component: () => <MerchantPickupPointManagement />,
meta: { title: '商家自提点管理' },
children: [
{
path: 'pickup-point-list', // 子路由同样遵循
component: () => <PickupPointListPage />,
meta: { title: '自提点列表' },
},
],
};
// bad
const routes = {
path: 'merchantPickupPointManagement', // 驼峰:URL可读性差
path: 'merchant_pickup_point_management', // 下划线:不符合网址规范
};
3.2 React 编码规范
3.2.1【推荐】优先使用 Functional Component + Hooks 开发
适配 React 18 + 最新特性(如并发渲染、useTransition),代码更简洁、复用性更高。
3.2.2【推荐】useEffect 必传依赖数组,省略需注释原因
避免无意义的重复执行,防止性能问题/无限循环。
typescript
// good(订单场景示例)
// 1. 依赖订单编号,仅编号变化时请求数据
useEffect(() => {
const fetchUserOrderList = async () => {
const res = await axios.get(`/api/order/list?userId=${userId}`);
setOrderList(res.data);
};
fetchUserOrderList();
}, [userId]); // 明确依赖:userId
// 2. 空依赖,仅挂载时初始化订单页
useEffect(() => {
const initializeOrderPage = () => {
setPageSize(10);
setCurrentPage(1);
};
initializeOrderPage();
}, []); // 空依赖:仅执行一次
// bad
useEffect(() => {
fetchUserOrderList(userId);
}); // 无依赖:每次渲染都执行,性能浪费
3.2.3【推荐】JSX 属性避免直接写对象/函数表达式
每次渲染生成新引用,导致组件不必要重渲染;多端场景需解耦视图与逻辑。
typescript
// good(订单新增自提点场景)
const MerchantPickupPoint: React.FC = () => {
const [displayList, setDisplayList] = useState([]);
const idRef = useRef(1);
const form = useForm(); // 假设使用AntD Form
// useCallback缓存函数,避免每次渲染生成新引用
const handleAddPickupPoint = useCallback(() => {
const newPoint = { id: idRef.current++, address: undefined };
setDisplayList(prev => [...prev, newPoint]);
form.setFieldValue(newPoint.id, newPoint.address);
}, [form]); // 明确依赖
return (
<Button onClick={handleAddPickupPoint}>
新增自提点
</Button>
);
};
// bad
const MerchantPickupPoint: React.FC = () => {
// 每次渲染创建新函数,触发Button重渲染
return (
<Button
onClick={() => {
const newPoint = { id: idRef.current++, address: undefined };
setDisplayList(prev => [...prev, newPoint]);
form.setFieldValue(newPoint.id, newPoint.address);
}}
>
新增自提点
</Button>
);
};
3.2.4【参考】React 函数组件推荐代码编写顺序
按"数据 → 逻辑 → 渲染"分层,提升代码可读性;支持按功能模块拆分(如订单数据/订单操作/订单渲染)。
typescript
// 按类型组织的推荐顺序:State → Custom Hooks → Internal Functions → Other Logic → Effects → JSX
const OrderStatistics: React.FC = () => {
// 1. State:订单统计数据
const [orderCount, setOrderCount] = useState(0);
const [totalAmount, setTotalAmount] = useState(0);
// 2. Custom Hooks:复用的订单统计逻辑
useOrderStatisticsRefresh(); // 自定义Hooks:订单统计刷新
// 3. Internal Functions:内部渲染/处理函数
const renderOrderCount = () => <p>今日订单数:{orderCount}</p>;
const renderTotalAmount = () => <p>今日交易额:{totalAmount} 元</p>;
// 4. Other Logic:辅助逻辑
const isAmountEmpty = totalAmount === 0;
// 5. Effects:副作用处理
useEffect(() => {
const fetchOrderStatistics = async () => {
const res = await axios.get('/api/order/statistics');
setOrderCount(res.data.count);
setTotalAmount(res.data.amount);
};
fetchOrderStatistics();
}, []);
// 6. JSX:最终渲染
return (
<div className="order-statistics">
{renderOrderCount()}
{renderTotalAmount()}
{isAmountEmpty && <p>今日暂无交易</p>}
</div>
);
};
3.2.5【推荐】高性能场景为组件添加 React.memo 优化
React.memo 仅对比 props 浅比较,需结合业务场景使用(复杂组件/高频渲染场景收益更高)。
typescript
// 订单列表项组件(高频渲染场景)
interface OrderItemProps {
orderNo: string;
amount: number;
}
// React.memo优化:仅props变化时重渲染
const OrderItem = React.memo(({ orderNo, amount }: OrderItemProps) => {
console.log(`仅订单 ${orderNo} 变化时执行`);
return <div className="order-item">{orderNo} - {amount} 元</div>;
});
// 父组件:订单列表
const OrderList: React.FC = () => {
const [orderList, setOrderList] = useState<OrderItemProps[]>([]);
// ... 其他逻辑
return (
<div>
{orderList.map(item => (
<OrderItem key={item.orderNo} orderNo={item.orderNo} amount={item.amount} />
))}
</div>
);
};
四、TypeScript 专项规范
4.1 TypeScript 命名规范
4.1.1【强制】class/enum/type/interface 使用 UpperCamelCase
类型/类名体现业务语义,避免通用命名。
typescript
// good(订单业务示例)
class UserOrderService {} // 订单服务类
enum OrderStatusType {} // 订单状态枚举
type MerchantOrderInfo = {}; // 商家订单信息类型
interface IOrderSubmitProps {} // 订单提交属性接口
// bad
class userOrderService {} // 小写开头,不符合约定
enum orderStatusType {} // 小写开头,语义不清晰
type merchantOrderInfo = {}; // 小写开头,类型命名不规范
4.1.2【推荐】枚举属性使用 UpperCamelCase(优先)/ UPPER_SNAKE_CASE
UpperCamelCase 在多组枚举值时可读性更佳,贴合 TS 主流实践。
typescript
// good - UpperCamelCase(推荐,订单状态枚举)
enum OrderStatus {
OrderPending = 'pending', // 订单待支付
OrderInProgress = 'in_progress', // 订单处理中
OrderCompleted = 'completed', // 订单已完成
}
// good - UPPER_SNAKE_CASE(备选)
enum OrderStatus {
ORDER_PENDING = 'pending',
ORDER_IN_PROGRESS = 'in_progress',
ORDER_COMPLETED = 'completed',
}
4.1.3【推荐】组件类型添加 Props 后缀,避免命名冲突
typescript
// good(订单提交按钮类型)
type OrderSubmitButtonProps = {
onClick: () => void;
orderNo: string;
disabled: boolean;
children: React.ReactNode;
};
const OrderSubmitButton: React.FC<OrderSubmitButtonProps> = ({ onClick, orderNo, disabled, children }) => {
return <button onClick={onClick} disabled={disabled}>{children}</button>;
};
// bad
type Props = { ... }; // 无业务语义,易冲突
type OrderSubmitButton = { ... }; // 与组件名重名,混淆
4.1.4【参考】类型标识(I 前缀/Enum 后缀)按需添加
无需强制统一,团队内部约定即可。
typescript
// 方式1:添加标识(区分类型/普通变量)
interface IOrderAddressInfo {} // I前缀:接口
enum PaymentTypeEnum {} // Enum后缀:枚举
// 方式2:不添加标识(简洁,团队约定清晰时推荐)
interface OrderAddressInfo {}
enum PaymentType {}
4.1.5【推荐】从接口平台导出 API 类型,减少手动编写
接口平台自动生成的类型准确率约 80%,使用时按需调整,提升开发效率。
接口平台定义订单API入参/出参
导出TS类型文件至项目
项目导入类型约束接口请求
业务侧校验/调整类型(如空值处理)
typescript
// 从接口平台导出的订单类型
import type { IGetMerchantOrderListRequest, IGetMerchantOrderListResponse } from '@ / api / order / types';
// 使用类型约束接口请求(避免any)
export const getMerchantOrderList = (params: IGetMerchantOrderListRequest) => {
return axios.get<IGetMerchantOrderListResponse>('/api/merchant/order/list', { params });
};
4.2 TypeScript 编码规范
4.2.1【强制】禁用大写内置对象作为类型
Number/String/Boolean 是 JS 内置对象,非类型名称,需使用小写基础类型。
typescript
// good
const orderNo: string = 'ORD20250307'; // 订单编号:string类型
const orderAmount: number = 199.9; // 订单金额:number类型
const isOrderValid: boolean = true; // 订单是否有效:boolean类型
// bad
const orderNo: String = 'ORD20250307'; // 内置对象,非类型
const orderAmount: Number = 199.9;
const isOrderValid: Boolean = true;
4.2.2【强制】类型断言使用 as 语法
JSX 中尖括号语法会冲突,as 语法更通用、可读性更好。
typescript
// good - as 语法(订单信息字符串解析)
let orderInfoStr: any = '{"orderNo":"ORD001","amount":199.9}';
let orderInfo = orderInfoStr as string; // 断言为字符串
let orderAmount = (JSON.parse(orderInfo) as { amount: number }).amount; // 嵌套断言
// bad - 尖括号语法(JSX中冲突)
let orderInfo = <string>orderInfoStr;
4.2.3【推荐】避免类型断言/非空断言(!),优先显式检查
断言会屏蔽 TS 类型校验,易导致运行时错误;确需使用时添加注释说明原因。
typescript
// good:显式类型检查(避免断言)
const getOrderAmount = (order: { amount?: number }) => {
if (order.amount === undefined) {
return 0; // 显式处理空值
}
return order.amount;
};
// bad:非空断言(屏蔽校验,order.amount为undefined时出错)
const getOrderAmount = (order: { amount?: number }) => {
return order.amount!; // !断言:强制认为非空
};
4.2.4【推荐】避免 any 类型,优先使用精确类型/unknown
any 会丧失 TS 类型校验能力,unknown 需显式类型检查后使用,更安全。
typescript
// good:使用unknown + 类型守卫
const parseOrderData = (data: unknown) => {
if (typeof data === 'object' && data !== null && 'orderNo' in data) {
return data as { orderNo: string }; // 窄化类型
}
throw new Error('订单数据格式错误');
};
// bad:使用any(无类型校验)
const parseOrderData = (data: any) => {
return data.orderNo; // 无校验,data无orderNo时运行时出错
};
4.2.5【参考】Custom Hook 返回值使用 as const 断言
让返回类型更精确,避免元组被扩展为宽泛数组类型。
typescript
// good:订单状态切换Hook(as const 精确类型)
const useOrderStatusToggle = (initialStatus: boolean) => {
const [isOrderPaid, setIsOrderPaid] = useState(initialStatus);
const toggleOrderStatus = useCallback(() => setIsOrderPaid(v => !v), []);
return [isOrderPaid, toggleOrderStatus] as const;
// 返回类型:readonly [boolean, () => void](精确元组)
};
// bad:无as const(类型宽泛)
const useOrderStatusToggle = (initialStatus: boolean) => {
const [isOrderPaid, setIsOrderPaid] = useState(initialStatus);
const toggleOrderStatus = useCallback(() => setIsOrderPaid(v => !v), []);
return [isOrderPaid, toggleOrderStatus];
// 返回类型:(boolean | (() => void))[](宽泛数组)
};
五、CSS 专项规范
5.1 CSS 命名规范
5.1.1【推荐】CSS Modules 类名使用 camelCase 命名
与 JS 代码命名风格统一,支持 styles.className 直接访问,IDE 自动补全更友好。
css
// good(订单页面样式)
/* order-page.module.less */
.orderPageNavTitle { // camelCase:业务语义+层级
font-size: 18px;
font-weight: 600;
}
/* order-page.tsx */
import styles from './ order-page.module.less';
const OrderPage: React.FC = () => {
return (
<div className={styles.orderPageNavTitle}>
商家订单管理
</div>
);
};
// bad
/* order-page.module.less */
.order-page-nav-title { // 短横线,需通过[]访问
font-size: 18px;
}
/* order-page.tsx */
import styles from './ order-page.module.less';
const OrderPage: React.FC = () => {
return (
<div className={styles['order-page-nav-title']}>
商家订单管理
</div>
);
};
5.2 CSS 编码规范
5.2.1【强制】页面 CSS 采用 CSS Modules 方案
避免样式全局污染,提升样式隔离性(如订单页样式仅作用于当前页面)。
5.2.2【强制】公共组件使用 BEM 命名,不使用 CSS Modules
公共组件需支持使用方覆盖样式,BEM 命名(块-元素-修饰符)更易扩展。
css
/* 公共订单按钮组件(BEM命名) */
.order-button { /* 块:组件根节点 */
padding: 8px 16px;
}
.order-button__icon { /* 元素:组件内部元素 */
margin-right: 4px;
}
.order-button--primary { /* 修饰符:主按钮样式 */
background: #1677ff;
color: #fff;
}
.order-button--disabled { /* 修饰符:禁用状态 */
background: #f5f5f5;
color: #999;
}
六、规范落地保障
6.1 ESLint 配置示例
针对规范配置核心规则,覆盖强制/推荐级约束:
javascript
// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
rules: {
// 基础规范:文件行数限制
'max-lines': ['warn', { max: 600, skipComments: true }],
// 圈复杂度限制
'complexity': ['warn', 15],
// React:useEffect 依赖检查
'react-hooks/exhaustive-deps': 'warn',
// TS:禁用any
'@typescript-eslint/no-explicit-any': ['warn', { ignoreRestArgs: true }],
// TS:禁用大写内置类型
'@typescript-eslint/ban-types': ['error', { types: { String: { message: '使用string而非String' }, Number: { message: '使用number而非Number' } } }],
},
};
6.2 代码评审(CR)要点
- 增量代码必须符合强制级规范,推荐级规范需说明未遵守原因
- 重点检查:命名规范、文件行数、圈复杂度、TS 类型使用
6.3 构建扫描
在 CI/CD 流程中添加规范扫描步骤,不符合强制级规范的代码禁止合并。
七、总结
7.1 核心规则速览
| 维度 | 核心规则 |
|---|---|
| 命名 | 文件/路由:kebab-case;组件/类型:PascalCase;Hooks:use+驼峰;ref:Ref结尾 |
| 编码 | 文件≤600行;函数圈复杂度≤15;useEffect必传依赖;JSX属性避免直接写函数 |
| TS | 禁用大写内置类型;优先as断言;避免any/非空断言;Custom Hook返回as const |
| CSS | 页面:CSS Modules(camelCase);公共组件:BEM命名 |
7.2 关键价值
- 统一的编码标准降低多团队协作成本,提升代码复用率
- 分级约束兼顾"规范落地"与"业务灵活性",避免过度约束
- 结合真实业务场景的示例,让规范更易落地、更贴合实际开发