React + TypeScript 编码规范|统一标准 & 高效维护

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.tsxuse-merchant-order-list.tsorder-utils.ts

2.2 编码规范

2.2.1【推荐】单文件代码量控制在 600 行以内

600 行内的文件更易进行代码评审、单元测试、重构;超过 1200 行需在代码注释中说明无法拆分的业务原因(新增 ESLint 规则:600 行触发 Warning,1200 行触发 Error)。

业务场景:电商订单页多状态(待付款/待发货/已完成)判断逻辑易超行,需拆分为 order-status-handler.tsorder-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 关键价值

  1. 统一的编码标准降低多团队协作成本,提升代码复用率
  2. 分级约束兼顾"规范落地"与"业务灵活性",避免过度约束
  3. 结合真实业务场景的示例,让规范更易落地、更贴合实际开发
相关推荐
lizhongxuan17 小时前
Claude Code 防上下文爆炸:源码级深度解析
前端·后端
柳杉18 小时前
震惊!字符串还能这么玩!
前端·javascript
是上好佳佳佳呀19 小时前
【前端(五)】CSS 知识梳理:浮动与定位
前端·css
wefly201719 小时前
纯前端架构深度解析:jsontop.cn,JSON 格式化与全栈开发效率平台
java·前端·python·架构·正则表达式·json·php
我命由我1234521 小时前
React - 类组件 setState 的 2 种写法、LazyLoad、useState
前端·javascript·react.js·html·ecmascript·html5·js
聊聊MES那点事21 小时前
JavaScript图表控件AG Charts使用教程:使用AG Charts React实时更新柱状图
开发语言·javascript·react.js·图表控件
自由生长20241 天前
IndexedDB的观察
前端
IT_陈寒1 天前
Vite热更新坑了我三天,原来配置要这么写
前端·人工智能·后端
斯班奇的好朋友阿法法1 天前
离线ollama导入Qwen3.5-9B.Q8_0.gguf模型
开发语言·前端·javascript
掘金一周1 天前
每月固定续订,但是token根本不够用,掘友们有无算力焦虑啊 | 沸点周刊 4.2
前端·aigc·openai