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. 结合真实业务场景的示例,让规范更易落地、更贴合实际开发
相关推荐
时光少年2 小时前
Android 视频分屏性能优化——GLContext共享
前端
IT_陈寒2 小时前
JavaScript开发者必知的5个性能杀手,你踩了几个坑?
前端·人工智能·后端
跟着珅聪学java2 小时前
Electron 精美菜单设计
运维·前端·数据库
日光倾3 小时前
【Vue.js 入门笔记】闭包和对象引用
前端·vue.js·笔记
一只程序熊3 小时前
UniappX 未找到 “video“ 组件,已自动当做 “view“ 组件处理。请确保代码正确,或重新生成自定义基座后再试。
前端
林小帅3 小时前
【笔记】xxx 技术分享文档模板
前端
雾岛心情3 小时前
【HTML&CSS】HTML为文字添加格式和内容
前端·css·html
心.c3 小时前
如何在项目中减少 XSS 攻击
前端·xss
Rsun045513 小时前
Vue相关面试题
前端·javascript·vue.js