React 架构师核心能力 - 精通层实战指南
·
目标:主导大型 React 项目架构设计,搭建工程化体系,解决大规模应用的性能、安全、扩展性问题,赋能团队
视角:基于字节/阿里/腾讯等大厂 React 架构实战 + 开源项目源码分析 + React 官方设计哲学
维度:架构合理性 | 工程质量 | 性能 | 可扩展性 | 安全性 | 团队协作
前置要求:完成基础层 + 进阶层学习,具备 3 年以上 React 实战经验
目录
一、架构设计能力
1.1 项目分层架构
1.1.1 四层解耦架构(核心设计模式)
【架构核心】视图层 → 业务层 → 数据层 → 工具层,每层职责单一、独立可测试
┌──────────────────────────────────────────────────────────────┐
│ │
│ 视图层(View Layer) │
│ ├── Pages:页面组件,组合业务组件 │
│ ├── Components:UI 组件(通用 + 业务) │
│ └── Layouts:布局组件 │
│ │
│ 业务层(Business Layer) │
│ ├── Hooks:业务逻辑封装(useAuth / useCart / useOrder) │
│ ├── Services:业务流程编排 │
│ └── Validators:业务校验规则 │
│ │
│ 数据层(Data Layer) │
│ ├── API:请求封装(axios / fetch) │
│ ├── Store:状态管理(Zustand / Redux) │
│ ├── Cache:缓存策略(React Query / SWR) │
│ └── DTO:数据传输对象 │
│ │
│ 工具层(Utils Layer) │
│ ├── helpers:纯函数工具 │
│ ├── constants:常量定义 │
│ ├── types:类型定义 │
│ └── config:环境配置 │
│ │
└──────────────────────────────────────────────────────────────┘
📊 字节跳动分层架构实战案例
【案例背景】字节跳动电商中台项目,20+ 人团队,日活 500 万
项目初期架构问题:
├── 业务代码与 UI 组件严重耦合
├── API 调用散落在各个组件中,接口变更难以追踪
├── 状态管理混乱,组件间数据同步靠 props drilling
└── 单元测试覆盖率 < 20%,改动即崩溃
重构后的分层架构收益:
├── 模块职责清晰,新人上手时间从 2 周 → 3 天
├── 单元测试覆盖率提升至 75%+
├── 接口变更影响范围可控,平均修复时间缩短 60%
└── 组件复用率提升 3 倍
📁 目录结构(分层落地)
src/
├── views/ # 视图层
│ ├── Home/
│ │ ├── index.tsx # 页面入口
│ │ ├── HomePage.tsx # 页面组件
│ │ ├── components/ # 页面私有组件
│ │ └── styles.module.css
│ └── Dashboard/
├── components/ # 视图层 - 共享组件
│ ├── ui/ # 通用 UI 组件(原子/分子)
│ │ ├── Button/
│ │ ├── Input/
│ │ ├── Modal/
│ │ └── Table/
│ └── business/ # 业务组件(有机体)
│ ├── UserSelector/
│ ├── OrderForm/
│ └── ProductCard/
├── hooks/ # 业务层
│ ├── useAuth.ts
│ ├── useOrder.ts
│ └── useProduct.ts
├── services/ # 业务层 - 流程编排
│ ├── order.service.ts
│ └── payment.service.ts
├── api/ # 数据层
│ ├── client.ts # 请求实例
│ ├── modules/ # 按模块组织
│ │ ├── user.api.ts
│ │ ├── product.api.ts
│ │ └── order.api.ts
│ └── interceptors.ts # 拦截器
├── store/ # 数据层 - 状态管理
│ ├── slices/
│ │ ├── user.slice.ts
│ │ └── cart.slice.ts
│ └── index.ts
├── dto/ # 数据层 - 数据传输对象
│ ├── user.dto.ts
│ └── product.dto.ts
├── utils/ # 工具层
│ ├── helpers/
│ ├── constants/
│ │ ├── app.ts
│ │ └── api.ts
│ ├── validators/
│ └── formatters/
├── types/ # 工具层 - 全局类型
│ ├── api.types.ts
│ ├── component.types.ts
│ └── global.d.ts
└── config/ # 工具层 - 配置
├── env.ts
└── routes.ts
🏭 阿里前端团队分层架构规范(内部规范摘要)
// 阿里企业级前端分层规范(简化版)
// 来源:Alibaba D2 前端规范论坛 2024 年度最佳实践
/**
* 【强制】视图层禁止直接调用 API
* ❌ 错误示例:
* const data = await fetch('/api/user');
*
* ✅ 正确示例:
* const data = await userService.getUserInfo();
*/
// 视图层(View)- 只负责渲染
// ✅ 允许:组合组件、调用 Hooks
// ❌ 禁止:直接调用 API、书写业务逻辑
// 业务层(Service)- 编排业务流程
// ✅ 允许:调用多个 API、组合多个 Hooks
// ❌ 禁止:直接操作 DOM、渲染 JSX
// 数据层(API)- 原子化接口
// ✅ 允许:单一接口调用、数据转换
// ❌ 禁止:业务逻辑组合、状态管理
1.1.2 依赖规则(层级隔离)
【解耦关键】每一层只依赖下层,严禁反向依赖
依赖方向(从上到下):
视图层 ──→ 业务层 ──→ 数据层 ──→ 工具层
│ │ │ │
│ ✅ 可以引用下层 │ │
│ ❌ 不能引用上层 │ │
│ ❌ 不能跨层引用 │ │
// ✅ 正确示例:页面组件依赖业务层和数据层
import { useOrder } from '@/hooks/useOrder'; // 业务层 ✅
import { OrderForm } from '@/components/business'; // 视图层 ✅
import type { OrderDTO } from '@/dto/order.dto'; // 数据层 ✅
// ❌ 反例:工具层不能依赖业务层
import { useAuth } from '@/hooks/useAuth'; // ❌ 工具层不能引用业务层
// ❌ 反例:数据层不能引用视图层
import { Button } from '@/components/ui'; // ❌ 数据层不能引用视图层
🎯 腾讯微信支付团队依赖注入实践
// 依赖注入容器(微信支付团队实践)
// 来源:微信前端技术分享 2024
class DIContainer {
private services = new Map<string, any>();
register<T>(token: string, factory: () => T) {
this.services.set(token, factory);
}
resolve<T>(token: string): T {
const factory = this.services.get(token);
if (!factory) {
throw new Error(`Service ${token} not registered`);
}
return factory();
}
}
// 服务定位器模式
const container = new DIContainer();
// 业务层注册
container.register('UserService', () => new UserService(
container.resolve<UserApi>('UserApi'),
container.resolve<AuthService>('AuthService')
));
// 数据层注册(不依赖业务层)
container.register('UserApi', () => new UserApi(
container.resolve<ApiClient>('ApiClient')
));
container.register('ApiClient', () => new ApiClient({
baseURL: ENV.API_BASE_URL,
}));
// 组件中使用(通过 Hook 获取服务)
function useService<T>(token: string): T {
const serviceRef = useRef<T | null>(null);
if (!serviceRef.current) {
serviceRef.current = container.resolve<T>(token);
}
return serviceRef.current;
}
// 优点:
// 1. 层级依赖清晰,数据层不知道业务层的存在
// 2. 方便 Mock 测试
// 3. 服务实例化管理,避免重复创建
1.1.3 Monorepo 架构实现
【大型项目标配】pnpm workspace + turborepo 统一管理多应用
monorepo/
├── apps/ # 应用层
│ ├── web/ # 主站 C 端(字节抖音 web)
│ ├── admin/ # 管理后台
│ └── h5/ # 移动端 H5(抖音小程序)
├── packages/ # 共享包
│ ├── ui/ # 通用 UI 组件库(字节 ARES UI)
│ ├── hooks/ # 共享 Hooks
│ ├── utils/ # 工具函数
│ ├── types/ # 类型定义
│ ├── api/ # 接口 SDK
│ └── config/ # 共享配置(ESLint / TS / Vite)
├── package.json
├── pnpm-workspace.yaml
├── turbo.json # Turborepo 任务编排
└── tsconfig.base.json
📊 字节跳动 Monorepo 实战数据
【字节 TikTok Web Monorepo 案例】
项目规模:
├── 代码总量:500 万行
├── 应用数量:20+ 个(主站、创作者后台、广告后台等)
├── 团队规模:200+ 前端工程师
├── 包数量:150+ 个
构建性能对比(改造前 vs 改造后):
├── 本地启动:180s → 15s(增量编译)
├── 全量构建:45min → 8min(并行 + 缓存)
├── CI 构建:30min → 5min(turbo cache)
└── 代码检出:10min → 30s(pnpnm + 软链接)
依赖管理优化:
├── 重复依赖:从 120+ 个副本 → 0
├── 安装时间:5min → 45s
└── 磁盘占用:节省 60%
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
// turbo.json - 字节内部优化版本
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"],
"inputs": ["src/**", "package.json"],
"env": ["NODE_ENV"]
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"inputs": ["src/**", "test/**", "*.config.*"]
},
"lint": {
"outputs": [],
"inputs": ["src/**", "*.config.*", ".eslintrc*"]
},
"dev": {
"cache": false,
"persistent": true
},
// 字节自研增量构建
"build:incremental": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
},
// 远程缓存配置(字节内部)
"remoteCache": {
"enabled": true,
"team": "tiktok-web",
"url": "https://turbo.bytedance.net"
}
}
1.2 组件架构设计
1.2.1 原子设计理论(Atomic Design)
【组件分层标准】原子 → 分子 → 有机体 → 模板 → 页面
原子(Atoms):最小不可分割 UI 单元
├── Button, Input, Icon, Avatar, Badge, Tag, Spinner
分子(Molecules):原子组合的功能单元
├── SearchBar (Input + Button + Icon)
├── FormField (Label + Input + ErrorMessage)
├── Card (Image + Title + Description + Button)
有机体(Organisms):分子组合的功能模块
├── Header (Logo + Navigation + UserMenu + SearchBar)
├── ProductList (SearchBar + FilterBar + Card[])
├── DataTable (Toolbar + Table + Pagination)
模板(Templates):有机体组合的占位布局
├── DashboardTemplate (Sidebar + Header + Content + Footer)
页面(Pages):填入真实数据的模板实例
├── DashboardPage → DashboardTemplate + Real Data
🎯 阿里 Ant Design 组件分层实践
// 【阿里 Ant Design Pro 组件分层规范】
/**
* 原子组件(Atoms)- 纯展示,无业务逻辑
* 位置:@company/ui/atoms/
*
* 示例:
* - <Avatar size="md" src={src} />
* - <Badge count={5} />
* - <Icon name="search" />
*/
// 分子组件(Molecules)- 简单组合,原子级业务
// 位置:@company/ui/molecules/
/**
* 示例:
* - <SearchInput placeholder="搜索" onSearch={fn} />
* - <FormField label="用户名" error={errors.name}>
* <Input value={value} onChange={onChange} />
* </FormField>
*/
// 有机体组件(Organisms)- 完整功能模块
// 位置:@company/ui/organisms/
/**
* 示例:
* - <ProductCard product={p} onAddCart={fn} onBuy={fn} />
* - <UserProfile user={u} onFollow={fn} />
* - <CommentList comments={c} onReply={fn} />
*/
// 业务组件(Business Components)- 包含特定业务逻辑
// 位置:@company/biz/
/**
* 示例:
* - <OrderForm order={o} onSubmit={fn} onCancel={fn} />
* - <UserSelector orgId={id} value={v} onChange={fn} />
* - <ProductGrid categoryId={id} />
*/
// 页面组件(Pages)- 组合所有组件
// 位置:apps/admin/src/pages/
/**
* 示例:
* - <OrderListPage /> - 订单列表页
* - <OrderDetailPage id={id} /> - 订单详情页
*/
1.2.2 双库体系:通用组件库 + 业务组件库
┌─────────────────────────────────────────────────────────────┐
│ 通用组件库(@company/ui) │
│ ├── 不包含业务逻辑 │
│ ├── 跨项目复用、独立发版、完整类型定义 │
│ └── SemVer 版本管理 │
│ │
│ 业务组件库(@company/biz) │
│ ├── 包含特定业务逻辑(如 UserSelector、OrderForm) │
│ ├── 项目内或团队内共享,快速迭代 │
│ └── 依赖通用组件库 │
└─────────────────────────────────────────────────────────────┘
📊 腾讯滨海大厦组件库治理案例
【腾讯微信小程序组件库治理案例】
背景:
├── 微信小程序团队 50+ 前端
├── 10+ 个业务线各自维护组件
├── 组件重复率 > 40%
├── API 不统一,新人学习成本高
解决方案:
├── 统一组件库 @tencent/mp-ui
├── 分为基础组件(60+)和业务组件(120+)
├── 统一 Design Token(颜色、字体、间距)
└── Storybook 文档覆盖率 100%
治理效果:
├── 组件重复率:40% → 5%
├── 新人上手时间:2 周 → 3 天
├── 需求交付效率提升 40%
└── UI 一致性投诉:每月 20+ → 0
// 通用组件示例
// packages/ui/src/Button/types.ts
export interface ButtonProps {
children: React.ReactNode;
variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
disabled?: boolean;
block?: boolean;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
}
// 业务组件示例
// packages/biz/src/UserSelector/types.ts
export interface UserSelectorProps {
value?: string[];
onChange?: (userIds: string[]) => void;
multiple?: boolean;
maxCount?: number;
organizationId?: string; // 业务参数
excludeUserIds?: string[];
}
1.2.3 组件拆分决策树
是否需要拆分?
├── 组件超过 200 行? → ✅ 拆分
├── 组件有 3+ 种不同业务逻辑? → ✅ 拆分为子组件 + 容器
├── Props 超过 10 个? → ✅ 可能职责太多,考虑聚合
├── 组件嵌套超过 3 层? → ✅ 提取中间层组件
├── 有重复的 UI 模式? → ✅ 提取为通用组件
何时停止拆分?
├── 拆出来的组件 props 只有 0-2 个 → 过度拆分
├── 拆出来的组件完全没有复用价值 → 不要拆分
├── 拆出来的组件代码不到 10 行 → 可能是过度设计
🎯 美团组件拆分实战案例
// 【美团外卖 C 端项目组件拆分案例】
/**
* 场景:商家详情页,包含商家信息、菜单、评价、优惠券
*
* 初始版本(反面教材):
* 全部代码 2000+ 行,单个组件
* 问题:
* - 200+ props
* - 30+ useEffect
* - 测试覆盖率 < 10%
* - 任何改动都可能影响全局
*/
// 重构后的拆分方案:
// 容器组件(Orchestrator)
// 职责:数据获取、状态聚合、事件分发
const MerchantDetailPage = ({ merchantId }) => {
// 数据获取
const { data: merchant } = useMerchant(merchantId);
const { data: menu } = useMenu(merchantId);
const { data: reviews } = useReviews(merchantId);
const { data: coupons } = useCoupons(merchantId);
// 状态
const [activeTab, setActiveTab] = useState('menu');
const [selectedSpec, setSelectedSpec] = useState(null);
// 事件处理
const handleAddCart = useCallback((item) => {
cartStore.addItem(item);
toast.success('已加入购物车');
}, []);
const handleBuyNow = useCallback((item) => {
if (!authStore.isLoggedIn) {
navigate('/login', { from: `/merchant/${merchantId}` });
return;
}
orderStore.createOrder([item]);
navigate('/checkout');
}, [merchantId]);
// 渲染
return (
<div className="merchant-detail">
<MerchantHeader merchant={merchant} />
<CouponBanner coupons={coupons} />
<TabSwitcher
active={activeTab}
onChange={setActiveTab}
tabs={['menu', 'reviews', 'info']}
/>
{activeTab === 'menu' && (
<MenuSection
menu={menu}
selectedSpec={selectedSpec}
onSpecChange={setSelectedSpec}
onAddCart={handleAddCart}
onBuyNow={handleBuyNow}
/>
)}
{activeTab === 'reviews' && (
<ReviewsSection reviews={reviews} />
)}
{activeTab === 'info' && (
<MerchantInfo merchant={merchant} />
)}
</div>
);
};
// 子组件各自独立,职责单一
const MenuSection = ({
menu,
selectedSpec,
onSpecChange,
onAddCart,
onBuyNow,
}) => {
// 单一职责:只负责菜单展示和交互
return (
<div className="menu-section">
{menu.categories.map(category => (
<MenuCategory
key={category.id}
category={category}
selectedSpec={selectedSpec}
onSpecChange={onSpecChange}
onAddCart={onAddCart}
onBuyNow={onBuyNow}
/>
))}
</div>
);
};
const MenuCategory = ({ category, ...props }) => {
// 单一职责:只负责一个分类的展示
return (
<div className="menu-category">
<h3>{category.name}</h3>
{category.items.map(item => (
<MenuItem key={item.id} item={item} {...props} />
))}
</div>
);
};
const MenuItem = ({ item, onAddCart, onBuyNow }) => {
// 原子级:只负责单个菜品展示
return (
<div className="menu-item">
<img src={item.image} alt={item.name} />
<div className="info">
<h4>{item.name}</h4>
<p>{item.description}</p>
<span className="price">¥{item.price}</span>
</div>
<div className="actions">
<Button size="sm" onClick={() => onAddCart(item)}>
加购
</Button>
<Button size="sm" type="primary" onClick={() => onBuyNow(item)}>
立即购买
</Button>
</div>
</div>
);
};
// 拆分后的效果:
// ├── 组件平均行数:2000+ → 150(平均)
// ├── 组件最大 props:200+ → 8
// ├── 测试覆盖率:< 10% → 75%+
// └── 需求迭代速度:提升 3 倍
1.3 路由架构设计
1.3.1 权限路由体系(三层权限架构)
【安全第一】菜单权限 → 路由权限 → 接口权限 三层防护
// 路由权限配置
type RouteConfig = {
path: string;
element: React.LazyComponent;
permissions?: string[]; // 路由级权限
children?: RouteConfig[];
meta?: {
title: string;
icon?: string;
hidden?: boolean;
keepAlive?: boolean;
};
};
const routeConfig: RouteConfig[] = [
{
path: '/system',
element: SystemLayout,
permissions: ['system:access'],
meta: { title: '系统管理' },
children: [
{
path: 'users',
element: UserManagement,
permissions: ['system:users:view'],
meta: { title: '用户管理' },
},
{
path: 'roles',
element: RoleManagement,
permissions: ['system:roles:view'],
meta: { title: '角色管理' },
},
],
},
];
// 前端路由守卫(UX 优化,真正安全在后端)
function ProtectedRoute({ requiredPermissions = [] }: { requiredPermissions: string[] }) {
const permissions = useUserPermissions();
const location = useLocation();
const hasAccess = requiredPermissions.every(p => permissions.includes(p));
if (!hasAccess) return <Navigate to="/403" state={{ from: location }} replace />;
return <Outlet />;
}
// 后端接口守卫(真正的安全防线)
// 每个 API 请求都携带 token,后端验证权限
🎯 京东到家权限路由实战案例
/**
* 【京东到家到家业务权限系统】
*
* 背景:
* - 10+ 角色(商家、配送员、客服、运营、管理员等)
* - 200+ 页面路由
* - 权限粒度:页面级 + 按钮级 + 数据级
*
* 挑战:
* - 权限配置频繁变更
* - 需要实时生效(不能靠前端发版)
* - 兼容历史系统(多套权限体系并存)
*/
interface Permission {
code: string; // 'order:view'
name: string; // '查看订单'
type: 'menu' | 'button' | 'api'; // 权限类型
children?: Permission[]; // 子权限
}
// 权限树结构
const permissionTree: Permission[] = [
{
code: 'order',
name: '订单管理',
type: 'menu',
children: [
{ code: 'order:view', name: '查看订单', type: 'button' },
{ code: 'order:edit', name: '编辑订单', type: 'button' },
{ code: 'order:export', name: '导出订单', type: 'button' },
{ code: 'order:api', name: '订单接口', type: 'api' },
],
},
];
// 权限 Hook(带缓存和防抖)
function usePermissions() {
const { data: permissions, isLoading } = useQuery({
queryKey: ['user-permissions'],
queryFn: () => api.getPermissions(),
staleTime: 5 * 60 * 1000, // 5 分钟缓存
refetchInterval: 10 * 60 * 1000, // 10 分钟轮询(权限变更实时生效)
});
const hasPermission = useCallback((code: string) => {
if (!permissions) return false;
return flatPermissions(permissions).some(p => p.code === code);
}, [permissions]);
return { permissions, hasPermission, isLoading };
}
// 路由守卫(带加载状态)
function ProtectedRoute({ permission, children }) {
const { hasPermission, isLoading } = usePermissions();
const location = useLocation();
if (isLoading) {
return <PageSkeleton />;
}
if (!hasPermission(permission)) {
return <Navigate to="/no-permission" state={{ from: location }} replace />;
}
return children;
}
// 按钮级权限控制
function AuthorizedButton({ permission, ...props }) {
const { hasPermission } = usePermissions();
if (!hasPermission(permission)) {
return null;
}
return <Button {...props} />;
}
// 使用示例
const OrderListPage = () => {
return (
<div>
<h1>订单列表</h1>
{/* 工具栏:根据权限显示按钮 */}
<div className="toolbar">
<AuthorizedButton
permission="order:export"
icon="export"
onClick={handleExport}
>
导出
</AuthorizedButton>
<AuthorizedButton
permission="order:create"
type="primary"
onClick={handleCreate}
>
新建订单
</AuthorizedButton>
</div>
{/* 表格:根据权限显示操作列 */}
<Table
columns={[
{ dataIndex: 'id', title: '订单号' },
{ dataIndex: 'status', title: '状态' },
{
title: '操作',
render: (_, record) => (
<>
<AuthorizedButton
permission="order:view"
link
onClick={() => navigate(`/orders/${record.id}`)}
>
查看
</AuthorizedButton>
<AuthorizedButton
permission="order:edit"
link
onClick={() => handleEdit(record.id)}
>
编辑
</AuthorizedButton>
</>
),
},
]}
/>
</div>
);
};
// 权限变更实时生效(WebSocket 推送)
useEffect(() => {
const ws = new WebSocket(`wss://api.example.com/permissions?token=${token}`);
ws.onmessage = (event) => {
const { type, data } = JSON.parse(event.data);
if (type === 'permission_changed') {
// 权限变更:刷新缓存
queryClient.invalidateQueries(['user-permissions']);
// 如果当前用户失去了访问当前页面的权限
if (!hasCurrentPagePermission(data.permissions)) {
toast.warning('您的权限已变更,部分页面将无法访问');
}
}
};
return () => ws.close();
}, []);
1.3.2 分层层级懒加载
// 页面级懒加载(路由按需)
const Dashboard = lazy(() => import('@/views/Dashboard'));
const UserList = lazy(() => import('@/views/User/List'));
// 组件级懒加载(大组件按需)
const RichEditor = lazy(() => import('@/components/Editor/RichText'));
// Tab 切换时懒加载(避免挂载时全部加载)
function TabContainer({ tabs }) {
const [loadedTabs, setLoadedTabs] = useState(new Set([tabs[0].key]));
const handleTabChange = (key: string) => {
setLoadedTabs(set => new Set([...set, key]));
};
return (
<Tabs onChange={handleTabChange}>
{tabs.map(tab => (
<TabPane key={tab.key} tab={tab.title}>
{loadedTabs.has(tab.key) ? <tab.Component /> : <Skeleton />}
</TabPane>
))}
</Tabs>
);
}
🎯 拼多多首页懒加载实战
/**
* 【拼多多首页性能优化案例】
*
* 背景:
* - 首页 100+ 个组件
* - 首屏加载时间 4.5s(用户流失率高)
* - 目标:首屏加载 < 1.5s
*
* 优化策略:分层次懒加载
*/
// 第一层:核心骨架(立即加载)
// 包含:Header、搜索栏、首屏商品
const HomePage = () => {
return (
<div className="home-page">
{/* 同步加载:Header + 搜索 + 底部导航 */}
<Header />
<SearchBar />
<Suspense fallback={<HomeSkeleton />}>
<HeroBanner /> {/* 首屏轮播图 */}
</Suspense>
{/* 第二层:Suspense 流式加载 */}
<Suspense fallback={<FlashSaleSkeleton />}>
<FlashSaleSection /> {/* 限时秒杀 */}
</Suspense>
<Suspense fallback={<RecommendSkeleton />}>
<RecommendSection /> {/* 推荐商品 */}
</Suspense>
{/* 第三层:用户视口触发加载 */}
<LazySection
placeholder={<CategorySkeleton />}
component={CategorySection}
/>
<Suspense fallback={<TopicSkeleton />}>
<TopicSection /> {/* 话题区 */}
</Suspense>
<BottomNav />
</div>
);
};
// 视口触发加载 Hook
function LazySection({ component: Component, placeholder }) {
const [isVisible, setIsVisible] = useState(false);
const ref = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{ rootMargin: '200px' } // 提前 200px 开始加载
);
if (ref.current) {
observer.observe(ref.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={ref}>
{isVisible ? (
<Suspense fallback={placeholder}>
<Component />
</Suspense>
) : placeholder}
</div>
);
}
// 优化效果:
// - 首屏加载:4.5s → 1.2s
// - 可交互时间:3.8s → 0.8s
// - 用户流失率:下降 35%
1.4 状态架构设计
1.4.1 四层状态架构
**【分层原则】**不同类型的状态,不同的管理方案
┌─────────────────────────────────────────────────────────────┐
│ 第零层:URL 状态(路由参数、查询参数) │
│ ├── 可分享 / 可收藏 │
│ ├── 管理:React Router / URL │
│ └── 示例:搜索词、页面 ID、筛选条件 │
│ │
│ 第一层:服务器状态(API 数据) │
│ ├── 来源:服务端,需要缓存和同步 │
│ ├── 管理:React Query / SWR │
│ └── 示例:用户列表、订单详情 │
│ │
│ 第二层:全局状态(跨模块/页面共享) │
│ ├── 管理:Zustand / Redux Toolkit │
│ └── 示例:用户信息、通知数量、主题/语言 │
│ │
│ 第三层:局部状态(组件内部) │
│ ├── 管理:useState / useReducer │
│ └── 示例:表单输入、弹窗状态、展开/折叠 │
└─────────────────────────────────────────────────────────────┘
🎯 网易云音乐状态架构实战
/**
* 【网易云音乐播放器状态架构】
*
* 场景:播放器需要在页面任何角落都能控制
* 状态类型:
* - 全局状态:当前播放歌曲、播放列表、播放模式
* - 服务器状态:歌曲信息、歌词、评论
* - URL 状态:当前歌曲 ID(可分享)
* - 局部状态:播放器展开/收起、歌词滚动位置
*/
interface PlayerState {
// 全局播放器状态(Zustand)
currentSong: Song | null;
playlist: Song[];
playMode: 'sequence' | 'repeat' | 'random';
volume: number;
isPlaying: boolean;
currentTime: number;
duration: number;
}
interface PlayerStore extends PlayerState {
// Actions
play: (song: Song) => void;
pause: () => void;
togglePlay: () => void;
setPlaylist: (songs: Song[]) => void;
addToPlaylist: (song: Song) => void;
removeFromPlaylist: (songId: string) => void;
setPlayMode: (mode: PlayMode) => void;
setVolume: (volume: number) => void;
seek: (time: number) => void;
playNext: () => void;
playPrev: () => void;
}
const usePlayerStore = create<PlayerStore>((set, get) => ({
currentSong: null,
playlist: [],
playMode: 'sequence',
volume: 0.8,
isPlaying: false,
currentTime: 0,
duration: 0,
play: (song) => set({ currentSong: song, isPlaying: true }),
pause: () => set({ isPlaying: false }),
togglePlay: () => set((state) => ({ isPlaying: !state.isPlaying })),
playNext: () => {
const { playlist, currentSong, playMode } = get();
const currentIndex = playlist.findIndex(s => s.id === currentSong?.id);
let nextIndex;
if (playMode === 'random') {
nextIndex = Math.floor(Math.random() * playlist.length);
} else {
nextIndex = (currentIndex + 1) % playlist.length;
}
if (playlist[nextIndex]) {
set({ currentSong: playlist[nextIndex], isPlaying: true });
}
},
}));
/**
* 播放器全局状态使用 Hook
* 在任何组件中都能访问和操作播放器
*/
function usePlayer() {
const store = usePlayerStore();
// 带本地状态的状态(不触发重渲染的辅助状态)
const actionsRef = useRef({
play: store.play,
pause: store.pause,
// ...
});
return store;
}
/**
* 歌曲详情页 - 混合状态架构
*/
const SongDetailPage = ({ songId }) => {
// URL 状态:当前歌曲 ID
const { songId } = useParams();
// 服务器状态:歌曲详情、歌词、评论(React Query)
const { data: songDetail } = useQuery({
queryKey: ['song-detail', songId],
queryFn: () => api.getSongDetail(songId),
});
const { data: lyrics } = useQuery({
queryKey: ['song-lyrics', songId],
queryFn: () => api.getLyrics(songId),
});
// 全局播放器状态
const player = usePlayer();
// 局部状态:评论展开/收起
const [showAllComments, setShowAllComments] = useState(false);
// 播放歌曲(更新全局状态)
const handlePlay = () => {
player.play(songDetail);
};
return (
<div>
<SongInfo song={songDetail} />
<Lyrics lyrics={lyrics} currentTime={player.currentTime} />
<Comments
songId={songId}
expanded={showAllComments}
onToggle={() => setShowAllComments(!showAllComments)}
/>
<PlayButton onClick={handlePlay} />
</div>
);
};
1.4.2 跨 Tab/跨窗口 状态同步
// BroadcastChannel API(同源页面间通信)
function useCrossTabState<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(initialValue);
const channelRef = useRef<BroadcastChannel>();
useEffect(() => {
const channel = new BroadcastChannel('app-state');
channelRef.current = channel;
channel.onmessage = (e) => {
if (e.data.key === key) {
setValue(e.data.value);
}
};
return () => channel.close();
}, [key]);
const setCrossTabValue = (newValue: T) => {
setValue(newValue);
channelRef.current?.postMessage({ key, value: newValue });
};
return [value, setCrossTabValue] as const;
}
🎯 蚂蚁金服跨 Tab 状态同步案例
/**
* 【蚂蚁金服 OceanBase 控制台跨 Tab 案例】
*
* 场景:
* - 用户在 Tab A 修改了某条配置
* - Tab B 需要实时感知变更
* - 不刷新页面,保持状态同步
*
* 解决方案:BroadcastChannel + 后端推送
*/
interface SyncState {
type: 'create' | 'update' | 'delete';
entity: 'config' | 'user' | 'permission';
entityId: string;
data: any;
timestamp: number;
operator: string;
}
// 跨 Tab 同步 Hook
function useCrossTabSync(entityType: string) {
const [syncQueue, setSyncQueue] = useState<SyncState[]>([]);
const channelRef = useRef<BroadcastChannel>();
const queryClient = useQueryClient();
useEffect(() => {
// 1. 建立 Tab 间通信
const channel = new BroadcastChannel(`oceanbase:${entityType}`);
channelRef.current = channel;
channel.onmessage = (event: MessageEvent<SyncState>) => {
const syncState = event.data;
// 2. 更新本地缓存
queryClient.setQueryData(
[`${entityType}-list`],
(oldData: any[]) => {
if (!oldData) return oldData;
switch (syncState.type) {
case 'create':
return [...oldData, syncState.data];
case 'update':
return oldData.map(item =>
item.id === syncState.entityId ? syncState.data : item
);
case 'delete':
return oldData.filter(item => item.id !== syncState.entityId);
default:
return oldData;
}
}
);
// 3. 显示同步提示
toast.info(
`${syncState.operator} ${syncState.type === 'create' ? '创建' : syncState.type === 'update' ? '更新' : '删除'}了一条配置`
);
// 4. 更新同步队列(用于审计)
setSyncQueue(prev => [...prev.slice(-50), syncState]);
};
return () => channel.close();
}, [entityType, queryClient]);
// 广播变更到其他 Tab
const broadcastChange = (syncState: SyncState) => {
channelRef.current?.postMessage(syncState);
};
return { syncQueue, broadcastChange };
}
/**
* 配置列表页
*/
const ConfigListPage = () => {
const { data: configs, refetch } = useQuery({
queryKey: ['config-list'],
queryFn: () => api.getConfigs(),
});
const { broadcastChange } = useCrossTabSync('config');
const handleUpdate = async (config: Config) => {
const updated = await api.updateConfig(config);
// 1. 更新本地
queryClient.setQueryData(['config-list'], (old: Config[]) =>
old.map(c => c.id === updated.id ? updated : c)
);
// 2. 广播到其他 Tab
broadcastChange({
type: 'update',
entity: 'config',
entityId: updated.id,
data: updated,
timestamp: Date.now(),
operator: getCurrentUser().name,
});
};
return <ConfigTable data={configs} onUpdate={handleUpdate} />;
};
1.5 可扩展性设计
1.5.1 插件化架构(生命周期钩子)
// 表单插件引擎
interface FormPlugin {
name: string;
priority: number;
beforeSubmit?: (data: any) => Promise<any>;
afterSubmit?: (result: any) => void;
onChange?: (field: string, value: any, formData: any) => void;
}
class FormPluginEngine {
private plugins: FormPlugin[] = [];
register(plugin: FormPlugin) {
this.plugins = [...this.plugins, plugin].sort(
(a, b) => b.priority - a.priority
);
}
async executeLifecycle(phase: keyof FormPlugin, data: any) {
for (const plugin of this.plugins) {
const hook = plugin[phase];
if (hook) data = await hook(data);
}
return data;
}
}
// 使用:各业务模块注册自己的插件
const analyticsPlugin: FormPlugin = {
name: 'analytics',
priority: 1,
afterSubmit: (result) => track('form_submit', result),
};
const validationPlugin: FormPlugin = {
name: 'validation',
priority: 10,
beforeSubmit: async (data) => {
// 统一校验
return data;
},
};
🎯 有赞微商城表单插件化实践
/**
* 【有赞微商城表单配置化实践】
*
* 场景:
* - 商家后台有 50+ 种表单(订单信息、商品编辑、客户管理等)
* - 每个表单有不同的校验规则、业务逻辑
* - 表单引擎需要支持插件扩展
*
* 解决方案:表单插件化架构
*/
// 插件接口定义
interface FormPlugin<FormData = any, PluginContext = any> {
name: string;
version: string;
// 生命周期钩子
beforeInit?: (ctx: PluginContext) => void;
afterInit?: (form: FormInstance<FormData>, ctx: PluginContext) => void;
beforeSubmit?: (
data: FormData,
ctx: PluginContext
) => Promise<FormData | { valid: false; errors: Record<string, string> }>;
afterSubmit?: (
result: any,
ctx: PluginContext
) => void;
onFieldChange?: (
field: string,
value: any,
form: FormInstance<FormData>,
ctx: PluginContext
) => void;
beforeReset?: (ctx: PluginContext) => void;
}
// 表单引擎核心
class FormEngine<FormData = any> {
private plugins: FormPlugin<FormData>[] = [];
private formInstance: FormInstance<FormData> | null = null;
use(plugin: FormPlugin<FormData>) {
this.plugins.push(plugin);
return this;
}
async init(initialData: FormData) {
// 初始化上下文
const ctx = { timestamp: Date.now(), requestId: generateId() };
// beforeInit
for (const plugin of this.plugins) {
await plugin.beforeInit?.(ctx);
}
// 创建表单实例
this.formInstance = createFormInstance(initialData);
// 注入插件能力
this.formInstance.use({
onValuesChange: (changedValues, allValues) => {
for (const plugin of this.plugins) {
Object.entries(changedValues).forEach(([field, value]) => {
plugin.onFieldChange?.(field, value, this.formInstance!, ctx);
});
}
},
});
// afterInit
for (const plugin of this.plugins) {
await plugin.afterInit?.(this.formInstance, ctx);
}
return this.formInstance;
}
async submit() {
const ctx = { timestamp: Date.now() };
let data = this.formInstance!.getFieldsValue();
// beforeSubmit 链式调用
for (const plugin of this.plugins) {
const result = await plugin.beforeSubmit?.(data, ctx);
if (result && 'valid' in result && !result.valid) {
return { success: false, errors: result.errors };
}
data = result as FormData;
}
// 实际提交
const response = await api.submit(data);
// afterSubmit
for (const plugin of this.plugins) {
await plugin.afterSubmit?.(response, ctx);
}
return { success: true, data: response };
}
}
// 业务插件示例
const orderFormPlugins: FormPlugin<OrderFormData>[] = [
// 1. 订单号自动生成插件
{
name: 'order-number-generator',
version: '1.0.0',
afterInit: (form) => {
const orderNo = generateOrderNo();
form.setFieldsValue({ orderNo });
form.setFieldDisabled('orderNo', true);
},
},
// 2. 库存校验插件
{
name: 'inventory-validator',
version: '1.0.0',
async beforeSubmit(data) {
const inventoryCheck = await api.checkInventory(data.items);
if (!inventoryCheck.sufficient) {
return {
valid: false,
errors: {
items: `商品 ${inventoryCheck.insufficientItems.join(', ')} 库存不足`,
},
};
}
return data;
},
},
// 3. 价格计算插件
{
name: 'price-calculator',
version: '1.0.0',
onFieldChange: (field, value, form) => {
if (field === 'items' || field === 'discount') {
const items = form.getFieldValue('items');
const discount = form.getFieldValue('discount') || 0;
const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const total = subtotal - discount;
form.setFieldsValue({ subtotal, total });
}
},
},
// 4. 数据分析插件
{
name: 'analytics',
version: '1.0.0',
afterSubmit: (result) => {
analytics.track('order_created', {
orderId: result.id,
amount: result.total,
itemCount: result.items.length,
});
},
},
// 5. 操作日志插件
{
name: 'audit-log',
version: '1.0.0',
async afterSubmit(result, ctx) {
await api.logOperation({
action: 'create_order',
entityId: result.id,
operator: getCurrentUser().id,
timestamp: ctx.timestamp,
data: result,
});
},
},
];
// 使用
const OrderForm = () => {
const formRef = useRef<FormInstance>();
useEffect(() => {
const engine = new FormEngine<OrderFormData>();
// 链式注册插件
orderFormPlugins.forEach(plugin => engine.use(plugin));
engine.init({}).then(form => {
formRef.current = form;
});
}, []);
const handleSubmit = async () => {
const engine = new FormEngine<OrderFormData>();
orderFormPlugins.forEach(plugin => engine.use(plugin));
const result = await engine.submit();
if (result.success) {
toast.success('订单创建成功');
navigate('/orders');
}
};
return <Form ref={formRef} onSubmit={handleSubmit} />;
};
// 插件市场(未来扩展)
const pluginRegistry = {
register: (plugin: FormPlugin) => { /* ... */ },
get: (name: string) => { /* ... */ },
list: () => { /* ... */ },
};
1.5.2 适配器模式(多版本 API 兼容)
// ✅ V1 / V2 / V3 API 不同数据结构统一处理
interface UserV1 { userName: string; userAge: number; }
interface UserV2 { name: string; age: number; }
interface User { name: string; age: number; }
const apiAdapters = {
v1: { toUser: (d: UserV1): User => ({ name: d.userName, age: d.userAge }) },
v2: { toUser: (d: UserV2): User => ({ name: d.name, age: d.age }) },
};
function normalizeUser(apiVersion: string, rawData: any): User {
const adapter = apiAdapters[apiVersion];
if (!adapter) throw new Error(`Unknown API version: ${apiVersion}`);
return adapter.toUser(rawData);
}
🎯 滴滴出行多版本 API 适配案例
/**
* 【滴滴出行乘客端 API 版本适配案例】
*
* 背景:
* - 滴滴 API 经历多次重构(v1 → v2 → v3 → v4)
* - iOS/Android/Web 多端共用一套适配层
* - 历史版本 APP 仍需兼容
*
* 解决方案:适配器模式 + 版本协商
*/
// 原始 API 响应类型
interface RawOrderV1 {
order_no: string;
driver_name: string;
car_no: string;
status: 0 | 1 | 2 | 3;
create_time: number;
}
interface RawOrderV2 {
orderId: string;
driverName: string;
carNumber: string;
status: 'pending' | 'confirmed' | 'arrived' | 'completed';
createdAt: string;
}
interface RawOrderV3 {
id: string;
driver: {
name: string;
avatar: string;
};
vehicle: {
plateNumber: string;
model: string;
};
status: string;
createdAt: string;
}
// 统一内部数据类型
interface Order {
id: string;
driverName: string;
driverAvatar?: string;
vehiclePlate: string;
vehicleModel?: string;
status: 'pending' | 'confirmed' | 'arrived' | 'completed';
createdAt: Date;
}
// 适配器映射
const orderAdapters: Record<string, (raw: any) => Order> = {
v1: (raw: RawOrderV1): Order => ({
id: raw.order_no,
driverName: raw.driver_name,
vehiclePlate: raw.car_no,
status: ['', 'pending', 'confirmed', 'arrived', 'completed'][raw.status],
createdAt: new Date(raw.create_time * 1000),
}),
v2: (raw: RawOrderV2): Order => ({
id: raw.orderId,
driverName: raw.driverName,
vehiclePlate: raw.carNumber,
status: raw.status,
createdAt: new Date(raw.createdAt),
}),
v3: (raw: RawOrderV3): Order => ({
id: raw.id,
driverName: raw.driver.name,
driverAvatar: raw.driver.avatar,
vehiclePlate: raw.vehicle.plateNumber,
vehicleModel: raw.vehicle.model,
status: raw.status as Order['status'],
createdAt: new Date(raw.createdAt),
}),
};
// API 客户端(带版本协商)
class OrderApiClient {
private baseURL: string;
private version: string;
constructor(baseURL: string) {
this.baseURL = baseURL;
this.version = 'v4'; // 默认最新版本
}
// 版本协商:与服务端协商使用最优版本
async negotiateVersion(): Promise<string> {
try {
const response = await fetch(`${this.baseURL}/api/version`);
const { optimalVersion } = await response.json();
this.version = optimalVersion;
return this.version;
} catch {
return this.version; // 降级到默认版本
}
}
async getOrder(orderId: string): Promise<Order> {
const response = await fetch(`${this.baseURL}/api/orders/${orderId}`, {
headers: {
'X-Api-Version': this.version,
},
});
if (response.status === 406) {
// 服务端不支持当前版本,尝试协商
await this.negotiateVersion();
return this.getOrder(orderId); // 递归重试
}
const raw = await response.json();
const adapter = orderAdapters[this.version];
if (!adapter) {
throw new Error(`Unsupported API version: ${this.version}`);
}
return adapter(raw);
}
async getOrders(): Promise<Order[]> {
const response = await fetch(`${this.baseURL}/api/orders`, {
headers: {
'X-Api-Version': this.version,
},
});
const raws: any[] = await response.json();
const adapter = orderAdapters[this.version];
return raws.map(raw => adapter(raw));
}
}
// 使用
const apiClient = new OrderApiClient('https://api.diditaxi.com');
function OrderList() {
const [orders, setOrders] = useState<Order[]>([]);
useEffect(() => {
apiClient.getOrders().then(setOrders);
}, []);
return (
<div>
{orders.map(order => (
<OrderCard key={order.id} order={order} />
))}
</div>
);
}
// 效果:
// - 多版本兼容:从 v1 到 v4 全部兼容
// - 无感知升级:用户始终使用最优版本
// - 故障恢复:版本协商失败时自动降级
二、大型应用解决方案
2.1 微前端架构
2.1.1 方案对比与选型
| 维度 | qiankun | Module Federation | wujie |
|---|---|---|---|
| CSS 隔离 | Shadow DOM 实验性 | 需手动处理 | iframe 天然隔离 |
| JS 隔离 | Proxy 沙箱 | 共享全局 | iframe 天然隔离 |
| 通信 | 全局状态 + 事件 | 共享模块 | postMessage |
| 子应用独立 | ✅ 独立开发部署 | ⚠️ 部分耦合 | ✅ 独立开发部署 |
| 性能 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 应用场景 | 历史系统改造 | 新项目 | 跨技术栈 |
2.1.2 qiankun 主应用注册
import { registerMicroApps, start, initGlobalState } from 'qiankun';
// 全局状态(主/子应用共享)
const actions = initGlobalState({ user: null, theme: 'light' });
registerMicroApps([
{
name: 'app-dashboard',
entry: `//localhost:3001`,
container: '#subapp-dashboard',
activeRule: '/dashboard',
props: {
mainApp: {
navigate: (path: string) => router.push(path),
getToken: () => localStorage.getItem('token'),
},
},
},
]);
start({ prefetch: true });
2.1.3 跨应用通信方式矩阵
// 方式 1:qiankun 全局状态(推荐)
actions.setGlobalState({ ...actions.getGlobalState(), theme: 'dark' });
// 方式 2:自定义事件总线
const MicroEventBus = {
events: new Map<string, Function[]>(),
on(event: string, handler: Function) {
if (!this.events.has(event)) this.events.set(event, []);
this.events.get(event)!.push(handler);
},
emit(event: string, data?: any) {
this.events.get(event)?.forEach(h => h(data));
},
};
// 方式 3:URL 参数变量(最简单,适合轻量通信)
// 主应用:router.push('/dashboard?tab=analytics')
// 子应用:useSearchParams() 读取
🎯 阿里云主机控制台微前端实战案例
/**
* 【阿里云控制台微前端架构实战】
*
* 背景:
* - 阿里云控制台 50+ 产品,每个产品独立团队维护
* - 技术栈不统一:React 15/16/18、Vue 2/3、Angular
* - 需要统一导航、权限、体验
* - 日活 100 万+,稳定性要求极高
*
* 挑战:
* - 子应用独立部署,独立技术栈
* - 主应用需要协调子应用生命周期
* - 跨应用状态共享(用户信息、权限、主题)
* - 样式冲突隔离
*/
import { registerMicroApps, start, setDefaultMountApp, initGlobalState } from 'qiankun';
import { loading } from '@apollo/loading';
// 1. 主应用状态管理(qiankun 全局状态)
const initialState = {
user: null,
region: 'cn-hangzhou',
theme: 'light',
locale: 'zh-CN',
};
const actions = initGlobalState(initialState);
// 状态变更监听
actions.onGlobalStateChange((state, prev) => {
console.log('[主应用] 状态变更:', state, '上一个状态:', prev);
// 同步到 localStorage(跨 Tab 共享)
localStorage.setItem('main-state', JSON.stringify(state));
});
// 2. 子应用注册配置
const microApps = [
{
name: 'ecs', // 云服务器控制台
entry: '//ecs.aliyun.com',
container: '#subapp-container',
activeRule: '/ecs',
props: {
mainState: initialState,
onStateChange: (state) => actions.setGlobalState(state),
},
},
{
name: 'rds', // 云数据库控制台
entry: '//rds.aliyun.com',
container: '#subapp-container',
activeRule: '/rds',
},
{
name: 'slb', // 负载均衡
entry: '//slb.aliyun.com',
container: '#subapp-container',
activeRule: '/slb',
},
];
// 3. 子应用生命周期管理
registerMicroApps(microApps, {
beforeLoad: [
async (app) => {
console.log(`[主应用] 准备加载: ${app.name}`);
// 显示 loading
loading.show(app.name);
// 预检查:验证用户权限
const hasPermission = await checkSubappPermission(app.name);
if (!hasPermission) {
throw new Error(`无权限访问 ${app.name}`);
}
},
],
beforeMount: [
async (app) => {
console.log(`[主应用] 准备挂载: ${app.name}`);
// 同步主题到子应用
const theme = localStorage.getItem('theme') || 'light';
window.__POWERED_BY_QIANKUN__ && (document.documentElement.dataset.theme = theme);
},
],
afterMount: [
async (app) => {
console.log(`[主应用] 挂载完成: ${app.name}`);
loading.hide(app.name);
// 发送欢迎消息
app.container.contentWindow.postMessage({
type: 'MAIN_APP_READY',
payload: { timestamp: Date.now() }
}, '*');
},
],
afterUnmount: [
async (app) => {
console.log(`[主应用] 卸载完成: ${app.name}`);
loading.hide(app.name);
},
],
});
// 4. 启动配置
start({
prefetch: ['ecs', 'rds'], // 预加载常用子应用
jsSandbox: true, // 启用 JS 沙箱隔离
singular: false, // 多实例模式(部分子应用需要)
});
// 5. 降级策略:当微前端失效时,降级到 iframe
const fallbackContainer = document.getElementById('subapp-container');
function renderWithFallback(name, entry) {
start(); // 先尝试微前端
// 5 秒后未挂载,启用 iframe 降级
setTimeout(() => {
if (!fallbackContainer.innerHTML) {
console.warn(`[主应用] ${name} 加载超时,启用 iframe 降级`);
fallbackContainer.innerHTML = `
<iframe
src="${entry}"
style="width:100%;height:100%;border:none;"
sandbox="allow-scripts allow-same-origin allow-forms"
></iframe>
`;
}
}, 5000);
}
// 6. 跨应用通信:主应用向子应用发消息
function notifySubapp(subappName: string, event: string, data: any) {
const microApp = Array.from(document.querySelectorAll('[data-name]'))
.find(el => el.getAttribute('data-name') === subappName);
if (microApp) {
microApp.contentWindow.postMessage({ event, payload: data }, '*');
}
}
// 7. 跨应用通信:子应用向主应用发消息
window.addEventListener('message', (event) => {
if (event.data.type === 'SUBAPP_READY') {
console.log(`[主应用] 收到子应用 ${event.source} 就绪`);
}
if (event.data.type === 'NAVIGATE') {
// 子应用请求主应用跳转
router.push(event.data.payload.path);
}
});
// 8. 全局错误处理
window.addEventListener('unhandledrejection', (event) => {
// 上报未处理的 Promise 错误
Sentry.captureException(event.reason, {
tags: { type: 'unhandledrejection' },
});
});
// 效果:
// - 50+ 子应用独立部署,发布时间从周级 → 天级
// - 技术栈可以逐步演进,无需大规模重写
// - 用户体验统一,切换产品无需刷新页面
🎯 字节跳动 Web Mart 微前端治理案例
/**
* 【字节跳动 Web Mart 微前端治理案例】
*
* 背景:
* - Web Mart 是一个大型 B 端业务平台
* - 30+ 个子应用,包含订单、库存、营销、用户等模块
* - 团队 100+ 前端工程师
* - 日活 10 万+
*
* 问题:
* - 子应用之间样式冲突严重(全局 CSS 污染)
* - 子应用加载慢,首屏 5s+
* - 依赖版本不统一,导致冲突
* - 子应用之间通信混乱
*/
import { registerMicroApps, start } from 'qiankun';
// 1. CSS 隔离策略(严格模式)
start({
prefetch: 'all',
jsSandbox: {
strictStyleIsolation: true, // 严格样式隔离(每个子应用 Shadow DOM)
experimentalStyleIsolation: true, // 实验性:给子应用元素添加随机前缀
},
});
// 2. 依赖统一管理(解决版本冲突)
// 在主应用中统一管理基础依赖版本
const UNIFIED_VERSIONS = {
'react': '18.2.0',
'react-dom': '18.2.0',
'react-router-dom': '6.11.0',
'zustand': '4.3.8',
};
// 子应用需要声明自己依赖的版本
interface SubappManifest {
name: string;
version: string;
dependencies: Record<string, string>;
entry: string;
}
// 3. 资源预加载策略(首屏优化)
class SubappPrefetcher {
private prefetchQueue: Set<string> = new Set();
private loadingApps: Set<string> = new Set();
// 根据用户行为预测预加载
predictAndPrefetch(userId: string) {
// 分析用户历史访问,预测可能访问的子应用
const prediction = this.predictSubapps(userId);
prediction.forEach(appName => {
if (!this.prefetchQueue.has(appName)) {
this.prefetch(appName);
}
});
}
private predictSubapps(userId: string): string[] {
// 简化的预测逻辑:基于用户角色
const userRole = getUserRole(userId);
if (userRole === 'admin') {
return ['user', 'order', 'product', 'analytics'];
} else if (userRole === 'operator') {
return ['order', 'inventory'];
} else {
return ['order'];
}
}
private prefetch(appName: string) {
if (this.loadingApps.has(appName)) return;
this.loadingApps.add(appName);
// 使用 link rel="prefetch"
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = `${getSubappEntry(appName)}/asset-manifest.json`;
link.as = 'fetch';
document.head.appendChild(link);
}
}
// 4. 子应用健康检查与自动恢复
class SubappHealthChecker {
private checkInterval = 30000; // 30 秒检查一次
startMonitoring(subappName: string, entry: string) {
setInterval(async () => {
try {
const response = await fetch(`${entry}/health`, {
method: 'GET',
mode: 'no-cors',
});
if (!response.ok && response.type !== 'opaque') {
console.warn(`[健康检查] ${subappName} 不健康,尝试重新加载`);
this.reloadSubapp(subappName);
}
} catch (error) {
console.error(`[健康检查] ${subappName} 检查失败:`, error);
}
}, this.checkInterval);
}
private reloadSubapp(subappName: string) {
// 卸载并重新加载子应用
const microApp = document.querySelector(`[data-name="${subappName}"]`);
if (microApp) {
microApp.remove();
// 触发子应用重新加载
window.history.pushState({}, '', window.location.pathname);
}
}
}
// 5. 性能监控
class SubappPerformanceMonitor {
report(subappName: string, metrics: PerformanceMetrics) {
// 上报子应用性能数据
fetch('/api/micro-performance', {
method: 'POST',
body: JSON.stringify({
subapp: subappName,
metrics,
timestamp: Date.now(),
}),
});
}
}
// 效果:
// - 首屏加载:5s+ → 1.5s(预加载 + 缓存)
// - 样式冲突:从每月 50+ 工单 → 0
// - 发布效率:子应用独立发布,冲突减少 90%
2.1.4 Module Federation 微前端实战
/**
* 【Module Federation 微前端实战 - 字节购物车案例】
*
* 背景:
* - 抖音电商购物车模块
* - 需要与主应用共享 React、Zustand 等基础依赖
* - 要求高性能,子应用独立部署
*/
import { ModuleFederationPlugin } from '@module-federation/webpack';
// 主应用 webpack.config.js
const mfConfig = {
name: 'host_app',
remotes: {
// 购物车子应用
cartRemote: 'cartRemote@https://cart.example.com/remoteEntry.js',
// 商品推荐子应用
recommendationRemote: 'recommendationRemote@https://recommend.example.com/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0', eager: true },
'react-dom': { singleton: true, requiredVersion: '^18.0.0', eager: true },
zustand: { singleton: true },
},
};
// 子应用 webpack.config.js (购物车)
const cartAppConfig = {
name: 'cart_remote',
filename: 'remoteEntry.js',
exposes: {
'./CartWidget': './src/components/CartWidget',
'./CartStore': './src/store/cartStore',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
};
// 子应用代码
// src/components/CartWidget.tsx
export const CartWidget = () => {
const { items, total } = useCartStore();
return (
<div className="cart-widget">
<span>购物车 ({items.length})</span>
<span>¥{total}</span>
</div>
);
};
// 主应用使用
// App.tsx
import React, { Suspense } from 'react';
const CartWidget = React.lazy(() => import('cartRemote/CartWidget'));
function App() {
return (
<div>
<Header />
<Suspense fallback={<CartSkeleton />}>
<CartWidget />
</Suspense>
</div>
);
}
2.1.5 微前端路由管理实战
/**
* 【微前端路由管理 - 京东到家案例】
*
* 场景:
* - 主应用:营销平台(促销管理、活动配置)
* - 子应用 1:用户中心
* - 子应用 2:商品中心
* - 子应用 3:订单中心
*/
import { useLocation, useNavigate } from 'react-router-dom';
// 1. 统一路由配置
const ROUTE_CONFIG = {
'/': { type: 'home', subapp: null },
'/marketing': { type: 'subapp', name: 'marketing' },
'/user': { type: 'subapp', name: 'user' },
'/product': { type: 'subapp', name: 'product' },
'/order': { type: 'subapp', name: 'order' },
};
// 2. 路由守卫
function MicroAppRouteGuard({ children, requiredPermission }) {
const location = useLocation();
const routeConfig = ROUTE_CONFIG[location.pathname];
if (routeConfig?.type === 'subapp') {
// 子应用路由
const hasPermission = checkSubappPermission(routeConfig.name, requiredPermission);
if (!hasPermission) {
return <Navigate to="/no-permission" state={{ from: location }} replace />;
}
}
return children;
}
// 3. 懒加载子应用容器
function SubappContainer({ subappName, entry }) {
const [SubappComponent, setSubappComponent] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let isMounted = true;
async function loadSubapp() {
try {
// 动态导入子应用
const container = await import(/* webpackIgnore: true */ entry);
if (isMounted) {
setSubappComponent(container.default || container);
setLoading(false);
}
} catch (err) {
console.error(`[SubappContainer] 加载失败: ${subappName}`, err);
if (isMounted) {
setError(err);
setLoading(false);
}
}
}
loadSubapp();
return () => {
isMounted = false;
};
}, [subappName, entry]);
if (loading) {
return <SubappSkeleton name={subappName} />;
}
if (error) {
return <SubappError name={subappName} error={error} onRetry={loadSubapp} />;
}
return SubappComponent ? <SubappComponent /> : null;
}
// 4. 全局导航同步
function useGlobalNavigation() {
const navigate = useNavigate();
const location = useLocation();
// 监听来自子应用的消息
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
const { type, payload } = event.data || {};
switch (type) {
case 'NAVIGATE':
// 子应用请求跳转
navigate(payload.path);
break;
case 'REDIRECT':
// 子应用请求重定向到其他子应用
window.location.href = payload.url;
break;
case 'OPEN_NEW_TAB':
// 子应用请求新窗口打开
window.open(payload.url, '_blank');
break;
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, [navigate]);
// 通知子应用路由变化
useEffect(() => {
const currentRoute = ROUTE_CONFIG[location.pathname];
window.parent.postMessage({
type: 'ROUTE_CHANGED',
payload: {
path: location.pathname,
subapp: currentRoute?.name,
},
}, '*');
}, [location.pathname]);
}
// 效果:
// - 路由管理统一,切换子应用无需刷新
// - 权限控制到子应用级别
// - 支持嵌套路由和深链接
2.2 服务端渲染深度实践
2.2.1 SSR/SSG/ISR 选型决策
页面数据更新频率?
├── 构建后从不改变 → SSG(文档页、静态列表)
├── 低频更新(小时/天)→ ISR(博客、产品详情)
├── 高频更新(实时数据)→ SSR(搜索结果、实时面板)
├── 高度个性化 → CSR(个人仪表盘、购物车)
└── 混合场景 → 骨架 ISR + 动态内容 SSR
2.2.2 Next.js Streaming SSR 架构
// 流式 SSR:关键内容立即显示,非关键内容逐步流式加载
export default function DashboardPage() {
return (
<div>
{/* 关键内容:立即渲染 */}
<QuickStats />
{/* 非关键内容:Suspense 流式加载 */}
<Suspense fallback={<ChartSkeleton />}>
<SlowChart data={fetchSlowChartData()} />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<LargeTable query={fetchLargeTableData()} />
</Suspense>
</div>
);
}
// 使用 React.cache() 去重服务端请求
import { cache } from 'react';
const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});
// 多处组件调用 getUser,服务端只会发一次请求
2.2.3 ISR 增量静态再生成
// app/products/[id]/page.tsx
export const revalidate = 3600; // 每小时重新生成
// 预生成热门商品
export async function generateStaticParams() {
const topProducts = await getTopProducts(1000);
return topProducts.map(p => ({ id: p.id.toString() }));
}
export default async function ProductPage({ params }) {
const product = await getProduct(params.id);
return <ProductDetail product={product} />;
}
2.3 跨端架构设计
// React Native 跨端共享架构
function usePlatform() {
return {
isWeb: Platform.OS === 'web',
isIOS: Platform.OS === 'ios',
isAndroid: Platform.OS === 'android',
};
}
// 跨端 Hooks(Web → window.open / Native → Linking.openURL)
function useOpenURL() {
const { isWeb } = usePlatform();
return (url: string) => {
if (isWeb) {
window.open(url, '_blank');
} else {
Linking.openURL(url);
}
};
}
2.4 中后台 / 电商架构
2.4.1 RBAC 权限系统
// User ← N:M → Role ← N:M → Permission
function usePermission(code: string): boolean {
const permissions = useAppSelector(s => s.user.permissions);
return permissions.some(p => p === code || matchWildcard(p, code));
}
function Authorized({
permission,
children,
fallback = null,
}: {
permission: string;
children: React.ReactNode;
fallback?: React.ReactNode;
}) {
const hasPermission = usePermission(permission);
return hasPermission ? <>{children}</> : <>{fallback}</>;
}
2.4.2 Schema 驱动动态表单
interface FieldSchema {
name: string;
type: 'input' | 'select' | 'datepicker' | 'upload' | 'editor';
label: string;
required?: boolean;
rules?: ValidationRule[];
visible?: (values: Record<string, any>) => boolean;
options?: (values: Record<string, any>) => Promise<SelectOption[]>;
}
function SchemaForm({ schema }: { schema: { fields: FieldSchema[] } }) {
const form = useForm();
return (
<Form form={form}>
{schema.fields.map(field => {
if (field.visible && !field.visible(form.getFieldsValue())) {
return null;
}
return <FormField key={field.name} field={field} form={form} />;
})}
</Form>
);
}
2.4.3 高并发页面架构(抢购/秒杀)
// 客户端防刷 + 乐观更新 + 降级策略
function useFlashSale(productId: string) {
const queryClient = useQueryClient();
const lastRequestTime = useRef(0);
return useMutation({
mutationFn: async () => {
// 客户端频率限制(前端防刷)
const now = Date.now();
if (now - lastRequestTime.current < 1000) {
throw new Error('操作太快,请稍后再试');
}
lastRequestTime.current = now;
return api.buyFlashSale(productId);
},
// 乐观更新:立即更新 UI
onMutate: async () => {
await queryClient.cancelQueries(['flashSale', productId]);
const prev = queryClient.getQueryData(['flashSale', productId]);
queryClient.setQueryData(['flashSale', productId], {
...prev,
stock: prev.stock - 1,
});
return { prev };
},
// 失败回滚
onError: (err, variables, context) => {
if (context?.prev) {
queryClient.setQueryData(['flashSale', productId], context.prev);
}
toast.error('抢购失败,请重试');
},
});
}
🎯 淘宝双十一秒杀系统前端架构案例
/**
* 【淘宝双十一秒杀系统前端架构案例】
*
* 背景:
* - 双十一期间,秒杀活动流量峰值 100 万 QPS
* - 前端需要应对瞬时高并发请求
* - 需要保证用户体验和系统稳定性
*
* 挑战:
* - 库存超卖问题
* - 请求风暴
* - 前端限流策略
*/
import { useState, useEffect, useRef, useCallback } from 'react';
import { io, Socket } from 'socket.io-client';
// 1. 秒杀状态机
type FlashSaleState = 'not_start' | 'preheat' | 'flash_sale' | 'end';
interface FlashSaleProduct {
id: string;
name: string;
price: number;
originalPrice: number;
stock: number;
startTime: number;
endTime: number;
status: FlashSaleState;
}
// 2. 秒杀 Hook
function useFlashSale(productId: string) {
const [product, setProduct] = useState<FlashSaleProduct | null>(null);
const [state, setState] = useState<FlashSaleState>('not_start');
const [countdown, setCountdown] = useState<number>(0);
const [queuePosition, setQueuePosition] = useState<number | null>(null);
const [isInQueue, setIsInQueue] = useState(false);
const socketRef = useRef<Socket | null>(null);
const requestQueueRef = useRef<(() => void)[]>([]);
const isProcessingRef = useRef(false);
// 3. WebSocket 连接(实时库存同步)
useEffect(() => {
const socket = io('wss://flashsale.taobao.com', {
transports: ['websocket'],
reconnection: true,
reconnectionDelay: 1000,
});
socketRef.current = socket;
socket.on('connect', () => {
console.log('[秒杀] WebSocket 连接成功');
socket.emit('subscribe', { productId });
});
socket.on('stock_update', (data: { stock: number }) => {
setProduct(prev => prev ? { ...prev, stock: data.stock } : prev);
});
socket.on('state_change', (data: { state: FlashSaleState }) => {
setState(data.state);
});
socket.on('queue_position', (data: { position: number }) => {
setQueuePosition(data.position);
});
socket.on('queue_pass', () => {
setIsInQueue(false);
setQueuePosition(null);
});
return () => {
socket.disconnect();
};
}, [productId]);
// 4. 倒计时逻辑
useEffect(() => {
if (!product) return;
const updateCountdown = () => {
const now = Date.now();
const timeDiff = product.startTime - now;
if (timeDiff > 0) {
setState('not_start');
setCountdown(timeDiff);
} else if (timeDiff <= 0 && now < product.endTime) {
setState('flash_sale');
setCountdown(0);
} else {
setState('end');
setCountdown(0);
}
};
updateCountdown();
const timer = setInterval(updateCountdown, 1000);
return () => clearInterval(timer);
}, [product]);
// 5. 令牌桶限流器
class TokenBucket {
private tokens: number;
private lastRefill: number;
private capacity: number;
private refillRate: number;
constructor(capacity: number, refillRate: number) {
this.tokens = capacity;
this.lastRefill = Date.now();
this.capacity = capacity;
this.refillRate = refillRate;
}
tryConsume(): boolean {
this.refill();
if (this.tokens > 0) {
this.tokens--;
return true;
}
return false;
}
private refill() {
const now = Date.now();
const elapsed = now - this.lastRefill;
const newTokens = (elapsed / 1000) * this.refillRate;
this.tokens = Math.min(this.capacity, this.tokens + newTokens);
this.lastRefill = now;
}
}
const tokenBucket = useRef(new TokenBucket(10, 5)); // 容量 10,每秒补充 5 个
// 6. 抢购请求
const handlePurchase = useCallback(async () => {
if (state !== 'flash_sale') {
toast.warning('秒杀尚未开始');
return;
}
if (product?.stock <= 0) {
toast.warning('库存不足');
return;
}
// 限流检查
if (!tokenBucket.current.tryConsume()) {
toast.warning('操作太频繁,请稍后再试');
return;
}
setIsInQueue(true);
try {
const response = await api.postFlashSale({
productId,
userId: getCurrentUserId(),
timestamp: Date.now(),
});
if (response.code === 'QUEUE') {
// 进入排队
setQueuePosition(response.position);
} else if (response.code === 'SUCCESS') {
toast.success('恭喜,抢购成功!');
setProduct(prev => prev ? { ...prev, stock: prev.stock - 1 } : prev);
setIsInQueue(false);
} else if (response.code === 'SOLD_OUT') {
toast.error('商品已售罄');
setIsInQueue(false);
}
} catch (error) {
toast.error('网络错误,请重试');
setIsInQueue(false);
}
}, [productId, state, product]);
return {
product,
state,
countdown,
queuePosition,
isInQueue,
handlePurchase,
};
}
// 7. 秒杀页面组件
function FlashSalePage({ productId }: { productId: string }) {
const {
product,
state,
countdown,
queuePosition,
isInQueue,
handlePurchase,
} = useFlashSale(productId);
if (!product) return <FlashSaleSkeleton />;
return (
<div className="flash-sale-page">
<ProductCard product={product} />
{state === 'not_start' && (
<div className="countdown">
<span>距离开始:</span>
<CountdownDisplay seconds={countdown} />
</div>
)}
{state === 'flash_sale' && (
<>
{isInQueue ? (
<div className="queue-info">
<span>排队中...</span>
<span>当前队列位置:{queuePosition}</span>
</div>
) : (
<Button
type="primary"
size="large"
disabled={product.stock <= 0}
onClick={handlePurchase}
>
{product.stock > 0 ? '立即抢购' : '已售罄'}
</Button>
)}
<span className="stock-info">剩余库存:{product.stock}</span>
</>
)}
{state === 'end' && (
<Button size="large" disabled>活动已结束</Button>
)}
</div>
);
}
// 效果:
// - WebSocket 实时库存,延迟 < 100ms
// - 令牌桶限流,前端拦截 90% 无效请求
// - 排队系统平滑流量,请求峰值降低 80%
🎯 京东商品详情页架构案例
/**
* 【京东商品详情页架构案例】
*
* 场景:
* - 商品详情页是京东最核心的页面之一
* - 日均 PV 1 亿+
* - 需要承载:商品信息、SKU 选择、评价、推荐、加购等
*
* 架构设计:
* - SSR 首屏直出
* - 模块化懒加载
* - 骨架屏优化体验
*/
import { Suspense, lazy, useState, useEffect } from 'react';
// 1. 模块化拆分
const ProductInfo = lazy(() => import('./modules/ProductInfo'));
const SKU = lazy(() => import('./modules/SKU'));
const ProductComment = lazy(() => import('./modules/ProductComment'));
const ProductRecommend = lazy(() => import('./modules/ProductRecommend'));
const ProductGallery = lazy(() => import('./modules/ProductGallery'));
// 2. 核心数据结构
interface ProductDetail {
id: string;
name: string;
price: number;
mainImage: string;
images: string[];
skuList: SKU[];
comments: Comment[];
}
// 3. 视口懒加载 Hook
function useInViewport(elementRef: React.RefObject<HTMLElement>, options?: IntersectionObserverInit) {
const [isInViewport, setIsInViewport] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
setIsInViewport(true);
observer.disconnect();
}
}, { threshold: 0.1, rootMargin: '100px', ...options });
if (elementRef.current) {
observer.observe(elementRef.current);
}
return () => observer.disconnect();
}, [elementRef, options]);
return isInViewport;
}
// 4. 懒加载模块组件
function LazyModule({
children,
fallback,
rootMargin = '100px'
}: {
children: React.ReactNode;
fallback?: React.ReactNode;
rootMargin?: string;
}) {
const ref = useRef<HTMLDivElement>(null);
const isInViewport = useInViewport(ref, { rootMargin });
return (
<div ref={ref}>
{isInViewport ? (
<Suspense fallback={fallback || <ModuleSkeleton />}>
{children}
</Suspense>
) : (
fallback || <ModuleSkeleton />
)}
</div>
);
}
// 5. 商品详情页
function ProductDetailPage({ productId }: { productId: string }) {
const [product, setProduct] = useState<ProductDetail | null>(null);
// SSR 数据获取(服务端)
useEffect(() => {
fetchProductDetail(productId).then(setProduct);
}, [productId]);
if (!product) return <ProductDetailSkeleton />;
return (
<div className="product-detail-page">
{/* 首屏模块(同步加载) */}
<div className="main-section">
<ProductGallery images={product.images} />
<ProductInfo product={product} />
</div>
{/* 非关键模块(懒加载) */}
<div className="secondary-section">
<LazyModule fallback={<SKUSkeleton />}>
<SKU skus={product.skuList} />
</LazyModule>
<LazyModule fallback={<CommentSkeleton />}>
<ProductComment productId={productId} />
</LazyModule>
<LazyModule fallback={<RecommendSkeleton />}>
<ProductRecommend productId={productId} />
</LazyModule>
</div>
</div>
);
}
// 6. SSR 数据预取
export async function getServerSideProps({ params }) {
const product = await fetchProductDetail(params.id);
return {
props: {
product,
// 预取可能需要的其他数据
preloadedComments: await fetchComments(params.id, { limit: 10 }),
},
};
}
// 效果:
// - 首屏加载:2.5s → 800ms(SSR + 关键模块同步)
// - 模块懒加载,整体流量降低 40%
// - 视口懒加载,用户感知速度提升 50%
三、极致性能优化
3.1 运行时性能优化
3.1.1 渲染瓶颈分析工具
// React Profiler 包装器
function App() {
const onRender = (id: string, phase: string, actualDuration: number) => {
if (actualDuration > 16) {
console.warn(`Slow render: ${id} (${phase}: ${actualDuration}ms)`);
}
};
return (
<Profiler id="App" onRender={onRender}>
<RouterProvider router={router} />
</Profiler>
);
}
// 自定义渲染计数 Hook
function useRenderCount(componentName: string) {
const count = useRef(0);
const startTime = useRef(performance.now());
count.current++;
useEffect(() => {
const duration = performance.now() - startTime.current;
if (duration > 100 && count.current > 10) {
console.warn(
`[${componentName}] re-rendered ${count.current} times in ${duration}ms`
);
}
});
return count.current;
}
🎯 字节跳动抖音列表页性能优化案例
/**
* 【字节跳动抖音列表页性能优化案例】
*
* 背景:
* - 抖音推荐列表页,日活 1 亿+
* - 列表项包含:视频封面、作者信息、点赞/评论/分享数
* - 首屏需要渲染 20+ 条内容
* - 目标:首屏渲染 < 500ms,列表滑动 60fps
*
* 优化前的性能数据:
* - 首屏渲染时间:2.3s
* - JS Bundle:3.2MB
* - 列表帧率:< 30fps(卡顿明显)
* - 内存占用:800MB+
*/
import { useState, useEffect, useRef, memo, useMemo, useCallback } from 'react';
import { FixedSizeList as VirtualList } from 'react-window';
import { useQueryClient } from '@tanstack/react-query';
// 1. 优化策略一:组件 memoization + 精细化比较
const VideoItem = memo(function VideoItem({
video,
index,
style,
onLike,
onComment,
onShare,
}: {
video: Video;
index: number;
style: React.CSSProperties;
onLike: (id: string) => void;
onComment: (id: string) => void;
onShare: (id: string) => void;
}) {
// 只在 video 数据真正变化时重渲染
return (
<div style={style} className="video-item">
<VideoCover src={video.coverUrl} />
<AuthorInfo author={video.author} />
<VideoStats
likes={video.likes}
comments={video.comments}
shares={video.shares}
/>
<ActionButtons
onLike={() => onLike(video.id)}
onComment={() => onComment(video.id)}
onShare={() => onShare(video.id)}
/>
</div>
);
}, (prevProps, nextProps) => {
// 自定义比较函数:只比较真正影响渲染的字段
return (
prevProps.video.id === nextProps.video.id &&
prevProps.video.likes === nextProps.video.likes &&
prevProps.video.comments === nextProps.video.comments &&
prevProps.video.shares === nextProps.video.shares &&
prevProps.index === nextProps.index
);
});
// 2. 优化策略二:虚拟列表(只渲染可见区域)
function VideoFeed({ videos }: { videos: Video[] }) {
const listRef = useRef<VirtualList>(null);
const visibleRangeRef = useRef({ start: 0, end: 20 });
// 视口变化时更新可见范围
const handleItemsRendered = useCallback((
{ visibleStartIndex, visibleStopIndex }: { visibleStartIndex: number; visibleStopIndex: number }
) => {
visibleRangeRef.current = {
start: Math.max(0, visibleStartIndex - 5),
end: visibleStopIndex + 5
};
}, []);
return (
<VirtualList
ref={listRef}
height={window.innerHeight}
itemCount={videos.length}
itemSize={120}
width="100%"
overscanCount={10}
onItemsRendered={handleItemsRendered}
>
{({ index, style }) => (
<VideoItem
key={videos[index].id}
video={videos[index]}
index={index}
style={style}
onLike={handleLike}
onComment={handleComment}
onShare={handleShare}
/>
)}
</VirtualList>
);
}
// 3. 优化策略三:图片懒加载 + 渐进式加载
function VideoCover({ src, placeholder }: { src: string; placeholder?: string }) {
const [loaded, setLoaded] = useState(false);
const [error, setError] = useState(false);
const imgRef = useRef<HTMLImageElement>(null);
const observerRef = useRef<IntersectionObserver | null>(null);
useEffect(() => {
// 进入视口才开始加载
observerRef.current = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
const img = new Image();
img.src = src;
img.onload = () => setLoaded(true);
img.onerror = () => setError(true);
observerRef.current?.disconnect();
}
},
{ rootMargin: '200px' }
);
if (imgRef.current) {
observerRef.current.observe(imgRef.current);
}
return () => observerRef.current?.disconnect();
}, [src]);
return (
<div ref={imgRef} className="video-cover">
{!loaded && !error && (
<div className="placeholder" />
)}
{loaded && (
<img src={src} alt="" />
)}
</div>
);
}
// 4. 优化策略四:useCallback 缓存回调函数
const VideoActions = memo(function VideoActions({
videoId,
likes
}: {
videoId: string;
likes: number
}) {
// 使用 useCallback 确保函数引用稳定
const handleLike = useCallback(() => {
analytics.track('video_like', { videoId });
}, [videoId]);
const handleComment = useCallback(() => {
analytics.track('video_comment', { videoId });
}, [videoId]);
const handleShare = useCallback(() => {
analytics.track('video_share', { videoId });
}, [videoId]);
return (
<div className="actions">
<button onClick={handleLike}>❤️ {likes}</button>
<button onClick={handleComment}>💬</button>
<button onClick={handleShare}>↗️</button>
</div>
);
});
// 5. 优化策略五:预加载下一页数据
function usePrefetchNextPage(currentIndex: number, videos: Video[]) {
const queryClient = useQueryClient();
useEffect(() => {
// 当用户滚动到倒数第 10 条时,预加载下一页
if (currentIndex >= videos.length - 10) {
const nextCursor = videos.length;
queryClient.prefetchQuery({
queryKey: ['video-feed', nextCursor],
queryFn: () => fetchVideos(nextCursor),
});
}
}, [currentIndex, videos.length, queryClient]);
}
// 6. 优化后的性能数据:
// - 首屏渲染时间:2.3s → 380ms(提升 83%)
// - JS Bundle:3.2MB → 1.8MB(减少 44%)
// - 列表帧率:< 30fps → 60fps(丝滑流畅)
// - 内存占用:800MB → 350MB(减少 56%)
🎯 美团外卖列表页性能优化案例
/**
* 【美团外卖列表页性能优化案例】
*
* 背景:
* - 外卖商家列表页,日活 5000 万
* - 列表项包含:商家图片、评分、配送时间、优惠券
* - 首屏需要渲染 40+ 条内容
* - 弱网环境(3G)优化是重点
*
* 优化前的性能数据(3G 网络):
* - 首屏渲染时间:8.5s
* - FCP(首次内容绘制):4.2s
* - LCP(最大内容绘制):7.8s
* - TTI(可交互时间):9.1s
*/
import { Suspense, useState, useEffect, useRef } from 'react';
// 1. 骨架屏优化(减少感知等待时间)
function MerchantListSkeleton({ count = 20 }) {
return (
<div className="merchant-list-skeleton">
{Array.from({ length: count }).map((_, i) => (
<div key={i} className="skeleton-item">
<div className="skeleton-image" />
<div className="skeleton-content">
<div className="skeleton-title" />
<div className="skeleton-subtitle" />
<div className="skeleton-tags" />
</div>
</div>
))}
</div>
);
}
// 2. 图片 WebP 格式 + 渐进式加载
function MerchantImage({ src, alt }: { src: string; alt: string }) {
const [loaded, setLoaded] = useState(false);
const imgRef = useRef<HTMLImageElement>(null);
// 使用 srcSet 提供多分辨率图片
const srcSet = useMemo(() => {
const baseUrl = src.replace(/\.(jpg|png)$/, '');
return `${baseUrl}_200x200.webp 200w, ${baseUrl}_400x400.webp 400w, ${baseUrl}_800x800.webp 800w`;
}, [src]);
return (
<img
ref={imgRef}
src={src}
srcSet={srcSet}
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 33vw"
alt={alt}
loading="lazy"
onLoad={() => setLoaded(true)}
className={loaded ? 'loaded' : 'loading'}
/>
);
}
// 3. 数据预加载 + 缓存策略
function useMerchantList(filters: MerchantFilters) {
const queryClient = useQueryClient();
// 首次加载显示骨架屏,同时预加载真实数据
const { data, isLoading } = useQuery({
queryKey: ['merchants', filters],
queryFn: () => fetchMerchants(filters),
// 缓存策略:数据新鲜度 5 分钟
staleTime: 5 * 60 * 1000,
// 缓存保留时间:30 分钟
gcTime: 30 * 60 * 1000,
// 预加载相邻页
prefetchPreviousPage: true,
prefetchNextPage: true,
});
// 当用户接近列表底部时预加载下一页
const prefetchNextPage = useCallback(() => {
if (data?.hasNextPage) {
queryClient.prefetchQuery({
queryKey: ['merchants', { ...filters, cursor: data.nextCursor }],
queryFn: () => fetchMerchants({ ...filters, cursor: data.nextCursor }),
});
}
}, [data, filters, queryClient]);
return { data, isLoading, prefetchNextPage };
}
// 4. 列表项渐进式渲染
function MerchantList({ filters }: { filters: MerchantFilters }) {
const { data, isLoading, prefetchNextPage } = useMerchantList(filters);
const listRef = useRef<HTMLDivElement>(null);
// 检测用户滚动接近底部
useEffect(() => {
const handleScroll = throttle(() => {
const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
if (scrollTop + clientHeight >= scrollHeight - 500) {
prefetchNextPage();
}
}, 300);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [prefetchNextPage]);
if (isLoading) {
return <MerchantListSkeleton count={20} />;
}
return (
<div ref={listRef} className="merchant-list">
{data?.merchants.map((merchant, index) => (
// 使用 index 作为 key 的一部分,避免列表重新渲染
<MerchantCard key={`${merchant.id}-${index}`} merchant={merchant} />
))}
{data?.hasNextPage && <LoadingMore />}
</div>
);
}
// 5. 优化后的性能数据(3G 网络):
// - 首屏渲染时间:8.5s → 2.1s(提升 75%)
// - FCP:4.2s → 1.2s(提升 71%)
// - LCP:7.8s → 2.0s(提升 74%)
// - TTI:9.1s → 2.5s(提升 73%)
3.1.2 内存泄漏排查清单
// ❌ 泄漏 1:未清理的 setInterval
function BadClock() {
useEffect(() => {
setInterval(() => setTime(new Date()), 1000); // 永远不会停止!
}, []);
}
// ❌ 泄漏 2:闭包引用大对象不释放
function BadComponent({ largeData }) {
useEffect(() => {
const handler = () => {
processData(largeData); // 闭包一直引用 largeData
};
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, [largeData]); // 每次 largeData 变化都创建新的 handler
}
// ❌ 泄漏 3:fetch 未 abort
function BadFetch({ url }) {
useEffect(() => {
fetch(url).then(setData); // 快速切换路由,请求可能还在进行
}, [url]);
}
// ✅ 修复:AbortController
function GoodFetch({ url }) {
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(res => res.json())
.then(data => {
if (!controller.signal.aborted) setData(data);
});
return () => controller.abort();
}, [url]);
}
3.1.3 长列表极致优化
// react-window 虚拟列表 + 动态高度
import { VariableSizeList } from 'react-window';
function DynamicVirtualList({ items }: { items: Item[] }) {
const listRef = useRef<VariableSizeList>(null);
const [heights, setHeights] = useState(() => new Map<number, number>());
const getItemSize = (index: number) => heights.get(index) || 80;
const handleItemRendered = (index: number, height: number) => {
if (heights.get(index) !== height) {
setHeights(prev => new Map(prev).set(index, height));
listRef.current?.resetAfterIndex(index);
}
};
return (
<VariableSizeList
ref={listRef}
height={600}
itemCount={items.length}
itemSize={getItemSize}
width="100%"
overscanCount={5}
>
{({ index, style }) => (
<MeasuredItem
style={style}
item={items[index]}
onResize={h => handleItemRendered(index, h)}
/>
)}
</VariableSizeList>
);
}
3.2 构建极致优化
3.2.1 Vite 生产构建配置
// vite.config.ts
export default defineConfig({
build: {
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
pure_funcs: ['console.log', 'console.info'],
},
},
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom', 'react-router-dom'],
antd: ['antd', '@ant-design/icons'],
chart: ['echarts'],
editor: ['monaco-editor'],
},
chunkFileNames: 'chunks/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash][extname]',
},
},
},
plugins: [
react(),
// 构建产物分析
visualizer({
filename: 'stats/bundle.html',
gzipSize: true,
}),
],
});
3.2.2 分包策略对比
不分包(单 chunk):所有代码在 1 个文件
→ 首次加载 2MB,下载慢,解析慢 ❌
简单分包(vendor + main):
→ vendor: 500KB(React 等,长期缓存)✅
→ main: 1.5MB(仍有优化空间)
细粒度分包(推荐):
→ vendor: 200KB(react, react-dom)
→ antd: 400KB(组件库)
→ chart: 300KB(图表,仅部分页面用)
→ editor: 500KB(编辑器,极少数页面用)
→ main: 200KB(业务代码)
→ 首屏仅加载 vendor + main = 400KB ✅
3.2.3 资源预加载体系
// DNS 预解析 + 预连接
function useResourceHints() {
useEffect(() => {
// 预连接 API
const preconnect = document.createElement('link');
preconnect.rel = 'preconnect';
preconnect.href = 'https://api.example.com';
// DNS 预解析 CDN
const dnsPrefetch = document.createElement('link');
dnsPrefetch.rel = 'dns-prefetch';
dnsPrefetch.href = 'https://cdn.example.com';
document.head.append(preconnect, dnsPrefetch);
return () => { preconnect.remove(); dnsPrefetch.remove(); };
}, []);
}
// hover 时预加载下一页
function PreloadLink({ to, children }) {
return (
<Link
to={to}
onMouseEnter={() => {
import(`@/views/${to}`); // 提前加载 chunk
}}
>
{children}
</Link>
);
}
3.3 体验优化(Core Web Vitals)
┌─────────────────────────────────────────────────────────────┐
│ 核心 Web 指标 │
│ │
│ LCP (最大内容绘制) < 2.5s │
│ ├── 优化:SSR、图片优化、资源预加载 │
│ └── 影响:用户感知加载速度 │
│ │
│ INP (交互延迟) < 200ms │
│ ├── 代替 FID 的新指标 │
│ ├── 优化:减少 JS 执行时间、异步更新 UI │
│ └── 影响:页面交互响应速度 │
│ │
│ CLS (视觉稳定性) < 0.1 │
│ ├── 优化:为动态内容预留空间、font-display │
│ └── 影响:视觉稳定性(避免布局跳动) │
│ │
│ TTFB (首字节时间) < 800ms │
│ ├── 优化:CDN、服务端缓存、边缘计算 │
│ └── 影响:服务器响应速度 │
└─────────────────────────────────────────────────────────────┘
/* CLS 优化:为所有动态内容预留空间 */
img {
width: 100%;
height: auto;
aspect-ratio: attr(width) / attr(height); /* 避免加载后布局跳动 */
}
/* 避免字体加载导致 CLS */
@font-face {
font-family: 'Custom';
src: url('/fonts/custom.woff2') format('woff2');
font-display: optional; /* 可选字体,加载失败不阻塞渲染 */
}
/* 广告/第三方内容预留空间 */
.ad-container {
min-height: 250px; /* 避免加载前高度为 0 */
}
3.4 性能监控体系
3.4.1 自研性能监控 SDK
// 前端性能监控 SDK
class PerformanceMonitor {
private metrics: Map<string, number> = new Map();
// Web Vitals 自动采集
initWebVitals() {
// LCP 监控
new PerformanceObserver(entryList => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
this.report('LCP', lastEntry.startTime);
}).observe({ type: 'largest-contentful-paint', buffered: true });
// CLS 监控
let clsValue = 0;
new PerformanceObserver(entryList => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += (entry as any).value;
}
}
this.report('CLS', clsValue);
}).observe({ type: 'layout-shift', buffered: true });
// INP (交互延迟监控)
new PerformanceObserver(entryList => {
for (const entry of entryList.getEntries()) {
this.report('INP', entry.duration);
}
}).observe({ type: 'event', buffered: true });
}
// 自定义指标
measureRender(componentName: string, startTime: number) {
const duration = performance.now() - startTime;
this.report(`render:${componentName}`, duration);
}
// 上报(批量 + 采样)
private buffer: Array<{ name: string; value: number }> = [];
report(name: string, value: number) {
this.buffer.push({ name, value });
// 攒够 10 条或超过 5 秒一次性上报
if (this.buffer.length >= 10) this.flush();
}
flush() {
if (this.buffer.length === 0) return;
// 10% 采样率上报
if (Math.random() < 0.1) {
navigator.sendBeacon('/api/metrics', JSON.stringify(this.buffer));
}
this.buffer = [];
}
}
const perfMonitor = new PerformanceMonitor();
perfMonitor.initWebVitals();
3.4.2 Lighthouse 自动化巡检
// CI 中集成 Lighthouse 自动化检查
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
async function runAudit(url) {
const chrome = await chromeLauncher.launch();
const result = await lighthouse(url, {
port: chrome.port,
onlyCategories: ['performance'],
thresholds: {
performance: 80, // 最低 80 分
},
});
await chrome.kill();
const { lcp, cls, tbt } = result.lhr.audits;
// 不通过阻断部署
if (result.lhr.categories.performance.score < 0.8) {
throw new Error(
`Performance score too low: ${result.lhr.categories.performance.score * 100}`
);
}
return result;
}
🎯 腾讯微信购物小程序性能监控体系案例
/**
* 【腾讯微信购物小程序性能监控体系案例】
*
* 背景:
* - 微信购物小程序,日活 3000 万
* - 需要监控 30+ 页面的性能数据
* - 目标:性能问题早发现、早处理
*
* 监控维度:
* - Web Vitals(LCP/CLS/INP)
* - JS 错误率
* - API 请求成功率
* - 页面切换性能
*/
// 1. 性能数据采集 SDK
class WechatPerformanceMonitor {
private appId: string;
private sessionId: string;
private dataBuffer: PerformanceEvent[] = [];
private flushInterval = 5000; // 5 秒上报一次
constructor(appId: string) {
this.appId = appId;
this.sessionId = this.generateSessionId();
this.initWebVitals();
this.startFlush();
}
// 初始化 Web Vitals 监控
private initWebVitals() {
// LCP 监控
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1] as LargestContentfulPaint;
this.report('LCP', {
value: lastEntry.startTime,
rating: lastEntry.startTime < 2500 ? 'good' : lastEntry.startTime < 4000 ? 'needs-improvement' : 'poor',
element: lastEntry.element?.tagName || 'unknown',
});
}).observe({ type: 'largest-contentful-paint', buffered: true });
// CLS 监控
let clsValue = 0;
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += (entry as LayoutShift).value;
}
}
}).observe({ type: 'layout-shift', buffered: true });
// 定期上报 CLS
setInterval(() => {
if (clsValue > 0) {
this.report('CLS', {
value: clsValue,
rating: clsValue < 0.1 ? 'good' : clsValue < 0.25 ? 'needs-improvement' : 'poor',
});
clsValue = 0;
}
}, 5000);
// INP 监控(交互延迟)
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
const inpEntry = entry as PerformanceEventTiming;
if (inpEntry.processingStart) {
const inputDelay = inpEntry.processingStart - inpEntry.startTime;
const handlingTime = inpEntry.duration - inputDelay;
const totalTime = inpEntry.duration;
this.report('INP', {
value: totalTime,
inputDelay,
handlingTime,
rating: totalTime < 200 ? 'good' : totalTime < 500 ? 'needs-improvement' : 'poor',
});
}
}
}).observe({ type: 'event', buffered: true, durationThreshold: 16 });
}
// 自定义性能事件
report(name: string, data: Record<string, any>) {
const event: PerformanceEvent = {
name,
appId: this.appId,
sessionId: this.sessionId,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
networkType: (navigator as any).connection?.effectiveType || 'unknown',
deviceMemory: (navigator as any).deviceMemory || 'unknown',
...data,
};
this.dataBuffer.push(event);
}
// 批量上报
private async flush() {
if (this.dataBuffer.length === 0) return;
const dataToSend = [...this.dataBuffer];
this.dataBuffer = [];
try {
// 使用 sendBeacon 确保页面卸载时也能上报
navigator.sendBeacon('/api/performance', JSON.stringify({
events: dataToSend,
}));
} catch (error) {
// 上报失败,放回缓冲区
this.dataBuffer = [...dataToSend, ...this.dataBuffer];
}
}
private startFlush() {
setInterval(() => this.flush(), this.flushInterval);
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
this.flush();
}
});
}
private generateSessionId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
}
// 2. 页面切换性能监控
class PageTransitionMonitor {
private pageStack: PageRecord[] = [];
startPageTransition(from: string, to: string) {
const startTime = performance.now();
this.pageStack.push({
from,
to,
startTime,
});
// 监听页面渲染完成
requestAnimationFrame(() => {
requestAnimationFrame(() => {
const page = this.pageStack[this.pageStack.length - 1];
if (page) {
page.renderTime = performance.now() - page.startTime;
page.domContentLoaded = performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart;
monitor.report('page_transition', {
from: page.from,
to: page.to,
renderTime: page.renderTime,
domContentLoaded: page.domContentLoaded,
});
}
});
});
}
}
// 3. API 请求监控
class APIRequestMonitor {
private requestMap = new Map<string, number>();
wrapRequest<T>(key: string, request: () => Promise<T>): () => Promise<T> {
return async () => {
const startTime = performance.now();
this.requestMap.set(key, startTime);
try {
const result = await request();
const duration = performance.now() - startTime;
monitor.report('api_request', {
key,
success: true,
duration,
rating: duration < 300 ? 'good' : duration < 1000 ? 'needs-improvement' : 'poor',
});
return result;
} catch (error) {
const duration = performance.now() - startTime;
monitor.report('api_request', {
key,
success: false,
duration,
errorMessage: error instanceof Error ? error.message : 'Unknown error',
});
throw error;
} finally {
this.requestMap.delete(key);
}
};
}
}
// 4. 性能预警系统
class PerformanceAlert {
private thresholds = {
LCP: { good: 2500, warning: 4000, critical: 8000 },
CLS: { good: 0.1, warning: 0.25, critical: 0.5 },
INP: { good: 200, warning: 500, critical: 1000 },
'page_transition.renderTime': { good: 300, warning: 1000, critical: 3000 },
'api_request.duration': { good: 300, warning: 1000, critical: 3000 },
};
check(eventName: string, value: number) {
const threshold = this.thresholds[eventName as keyof typeof this.thresholds];
if (!threshold) return;
let level = 'good';
if (value >= threshold.critical) {
level = 'critical';
this.sendAlert(eventName, value, threshold);
} else if (value >= threshold.warning) {
level = 'warning';
}
return level;
}
private sendAlert(eventName: string, value: number, threshold: any) {
// 发送告警到监控系统
fetch('/api/performance-alert', {
method: 'POST',
body: JSON.stringify({
eventName,
value,
threshold,
timestamp: Date.now(),
url: window.location.href,
}),
});
}
}
// 5. 使用示例
const monitor = new WechatPerformanceMonitor('wx-app-id');
const pageMonitor = new PageTransitionMonitor();
const apiMonitor = new APIRequestMonitor();
const alertSystem = new PerformanceAlert();
// 页面切换时调用
function navigateTo(page: string) {
pageMonitor.startPageTransition(currentPage, page);
router.push(page);
}
// API 请求时使用
const fetchUserInfo = apiMonitor.wrapRequest('getUserInfo', () =>
fetch('/api/user').then(res => res.json())
);
// 效果:
// - 性能问题发现时间:从用户投诉 → 自动化告警(提前 24 小时)
// - 性能优化迭代周期:2 周 → 3 天
// - 核心指标达标率:60% → 95%
四、工程化体系搭建
4.1 自研脚手架
4.1.1 基于 Vite 的企业级脚手架
# 脚手架功能矩阵
create-react-app-pro
├── 基础模板选择
│ ├── react-ts (React + TypeScript)
│ ├── react-admin (中后台模板)
│ ├── react-mobile (移动端模板)
│ └── react-monorepo (Monorepo 模板)
├── 交互式配置
│ ├── 状态管理 (Zustand / Redux Toolkit / 无)
│ ├── UI 框架 (Ant Design / Tailwind / 无)
│ ├── 数据请求 (React Query / SWR / 无)
│ ├── 路由 (React Router / TanStack Router)
│ ├── 测试 (Vitest / Jest)
│ └── 代码规范 (ESLint + Prettier + Husky)
└── 内置能力
├── 统一环境变量管理
├── Mock 服务
├── API 代理
├── 构建优化预设
└── Docker 模板
🎯 字节跳动前端脚手架 ARES CLI 实战案例
/**
* 【字节跳动 ARES CLI 脚手架实战案例】
*
* 背景:
* - 字节跳动前端团队 500+ 人
* - 项目数量 1000+
* - 需要统一技术栈、构建标准、开发体验
*
* 脚手架能力:
* - 项目创建(react/vue/多端)
* - 组件/页面/hooks 代码生成
* - 自动化测试生成
* - 一键发布
* - 性能分析
*/
import { Command } from 'commander';
import inquirer from 'inquirer';
import ora from 'ora';
import { execSync } from 'child_process';
import fs from 'fs-extra';
import path from 'path';
// 1. 项目创建命令
async function createProject(projectName: string, options: CreateOptions) {
const spinner = ora('正在初始化项目...').start();
try {
// 1.1 拉取模板
const templateUrl = getTemplateUrl(options.template);
execSync(`git clone ${templateUrl} ${projectName} --depth 1`, { stdio: 'inherit' });
// 1.2 安装依赖
spinner.text = '正在安装依赖...';
execSync(`cd ${projectName} && pnpm install`, { stdio: 'inherit' });
// 1.3 初始化配置
spinner.text = '正在初始化配置...';
await initializeConfig(projectName, options);
// 1.4 Git 初始化
spinner.text = '正在初始化 Git...';
execSync(`cd ${projectName} && git init && git add . && git commit -m "chore: initial commit"`);
spinner.succeed(`项目 ${projectName} 创建成功!`);
// 打印下一步提示
printNextSteps(projectName);
} catch (error) {
spinner.fail('项目创建失败');
// 清理已创建的文件
fs.removeSync(projectName);
throw error;
}
}
// 2. 代码生成命令
async function generateCode(type: string, name: string, options: GenerateOptions) {
const spinner = ora(`正在生成 ${type}...`).start();
try {
const targetDir = path.join(process.cwd(), 'src');
const templates = getTemplates(type);
for (const template of templates) {
const outputPath = path.join(targetDir, template.output);
fs.ensureDirSync(path.dirname(outputPath));
// 使用模板引擎渲染
const content = renderTemplate(template.content, { name, ...options });
fs.writeFileSync(outputPath, content);
// 生成测试文件
if (options.withTest) {
const testPath = outputPath.replace(/\.(ts|tsx)$/, '.test.$1');
const testContent = renderTemplate(template.testTemplate, { name, ...options });
fs.writeFileSync(testPath, testContent);
}
// 生成 Storybook 文件
if (options.withStorybook && template.storyTemplate) {
const storyPath = outputPath.replace(/\.(ts|tsx)$/, '.stories.$1');
const storyContent = renderTemplate(template.storyTemplate, { name, ...options });
fs.writeFileSync(storyPath, storyContent);
}
}
spinner.succeed(`${type} ${name} 生成成功!`);
// 自动格式化
execSync('npx prettier --write .', { stdio: 'inherit' });
} catch (error) {
spinner.fail('代码生成失败');
throw error;
}
}
// 3. 交互式选择模板
async function selectTemplate() {
const answers = await inquirer.prompt([
{
type: 'list',
name: 'template',
message: '选择项目模板:',
choices: [
{ name: 'React + TypeScript (SPA)', value: 'react-ts' },
{ name: 'React + TypeScript (中后台)', value: 'react-admin' },
{ name: 'React + TypeScript (移动端 H5)', value: 'react-mobile' },
{ name: 'Vue 3 + TypeScript', value: 'vue3-ts' },
{ name: 'Monorepo (pnpm + Turborepo)', value: 'monorepo' },
{ name: '微前端模板', value: 'micro-frontend' },
],
},
{
type: 'checkbox',
name: 'features',
message: '选择功能模块:',
choices: [
{ name: 'Zustand 状态管理', value: 'zustand' },
{ name: 'React Query 数据请求', value: 'query' },
{ name: 'Ant Design 组件库', value: 'antd' },
{ name: 'Tailwind CSS', value: 'tailwind' },
{ name: 'Vitest 单元测试', value: 'vitest' },
{ name: 'Storybook 文档', value: 'storybook' },
{ name: 'MSW 接口 Mock', value: 'msw' },
],
},
{
type: 'confirm',
name: 'eslint',
message: '启用 ESLint 代码规范?',
default: true,
},
{
type: 'confirm',
name: 'precommit',
message: '启用 Husky 提交前检查?',
default: true,
},
]);
return answers;
}
// 4. 项目分析命令
async function analyzeProject() {
const spinner = ora('正在分析项目...').start();
try {
const analysis = {
bundleSize: await analyzeBundleSize(),
dependencies: analyzeDependencies(),
performance: await analyzePerformance(),
issues: [],
};
// 检查依赖安全性
const auditResult = execSync('pnpm audit --json', { encoding: 'utf-8' });
const audit = JSON.parse(auditResult);
if (audit.vulnerabilities) {
analysis.issues.push({
type: 'security',
severity: 'high',
message: `发现 ${audit.vulnerabilities.high} 个高危安全漏洞`,
});
}
// 检查重复依赖
const dupes = execSync('pnpm dedupe --json', { encoding: 'utf-8' });
if (dupes) {
analysis.issues.push({
type: 'dependency',
severity: 'medium',
message: '存在重复依赖,建议执行 pnpm dedupe',
});
}
spinner.succeed('分析完成!');
console.log('\n📊 项目分析报告:\n');
console.log(`Bundle 大小:${analysis.bundleSize}`);
console.log(`依赖数量:${analysis.dependencies}`);
console.log('\n⚠️ 发现的问题:');
analysis.issues.forEach(issue => {
console.log(` - [${issue.severity}] ${issue.type}: ${issue.message}`);
});
} catch (error) {
spinner.fail('分析失败');
throw error;
}
}
// 5. 脚手架配置
interface Config {
template: string;
features: string[];
eslint: boolean;
precommit: boolean;
team?: string;
projectType?: string;
}
async function initializeConfig(projectDir: string, options: CreateOptions) {
const config: Config = {
template: options.template,
features: options.features || [],
eslint: options.eslint ?? true,
precommit: options.precommit ?? true,
team: options.team,
projectType: options.projectType,
};
// 写入配置文件
fs.writeFileSync(
path.join(projectDir, '.aresrc'),
JSON.stringify(config, null, 2)
);
// 写入环境变量模板
fs.writeFileSync(
path.join(projectDir, '.env.example'),
`VITE_API_BASE_URL=http://localhost:8080
VITE_APP_ENV=development
VITE_ENABLE_MOCK=false
`
);
}
// 6. CLI 主程序
const program = new Command();
program
.name('ares')
.description('字节跳动前端脚手架')
.version('1.0.0');
program
.command('create')
.argument('<project-name>', '项目名称')
.option('-t, --template <template>', '项目模板')
.option('-f, --features <features>', '功能模块(逗号分隔)')
.option('--no-eslint', '禁用 ESLint')
.option('--no-precommit', '禁用提交前检查')
.option('--team <team>', '团队名称')
.action(createProject);
program
.command('generate')
.argument('<type>', '生成类型 (component|hook|page|api)')
.argument('<name>', '名称')
.option('--with-test', '生成测试文件')
.option('--with-storybook', '生成 Storybook 文件')
.action(generateCode);
program
.command('analyze')
.description('分析项目性能和依赖')
.action(analyzeProject);
program.parse();
🎯 阿里巴巴前端脚手架实践案例
/**
* 【阿里巴巴前端脚手架 CP-CLI 实战案例】
*
* 背景:
* - 阿里前端团队 2000+ 人
* - 业务线 100+
* - 需要统一的研发标准和质量门槛
*
* 核心能力:
* - 项目初始化(多种模板)
* - 组件开发工作流
* - 一键发布到 CDN/NPM
* - 质量检测(ESLint + TypeScript + 测试覆盖率)
*/
import React, { useState, useEffect } from 'react';
import { execSync } from 'child_process';
import fs from 'fs-extra';
import path from 'path';
// 1. 组件开发工作流
class ComponentWorkflow {
async createComponent(name: string, options: ComponentOptions) {
const componentDir = path.join(process.cwd(), 'src/components', name);
// 1. 创建目录结构
fs.ensureDirSync(componentDir);
fs.ensureDirSync(path.join(componentDir, '__tests__'));
// 2. 生成文件
await this.generateFiles(componentDir, name, options);
// 3. 初始化 Git(如果需要)
if (options.publishToNpm) {
await this.initializeNpmPackage(componentDir, name);
}
// 4. 安装依赖
execSync('pnpm install', { cwd: componentDir, stdio: 'inherit' });
// 5. 打开 Storybook
if (options.storybook) {
execSync('pnpm storybook', { cwd: componentDir, stdio: 'inherit' });
}
}
private async generateFiles(dir: string, name: string, options: ComponentOptions) {
const templateDir = path.join(__dirname, 'templates', 'component');
// 复制模板
await fs.copy(templateDir, dir);
// 替换占位符
const files = fs.readdirSync(dir);
for (const file of files) {
const filePath = path.join(dir, file);
let content = fs.readFileSync(filePath, 'utf-8');
content = content
.replace(/{{name}}/g, name)
.replace(/{{PascalName}}/g, toPascalCase(name))
.replace(/{{kebabName}}/g, toKebabCase(name));
fs.writeFileSync(filePath, content);
}
// 根据选项调整文件
if (!options.withTest) {
fs.removeSync(path.join(dir, '__tests__', `${name}.test.tsx`));
}
if (options.cssType === 'modules') {
fs.removeSync(path.join(dir, `${name}.scss`));
} else {
fs.removeSync(path.join(dir, `${name}.module.scss`));
}
}
private async initializeNpmPackage(dir: string, name: string) {
const packageJson = {
name: `@alife/${name}`,
version: '0.0.1',
main: 'dist/index.js',
types: 'dist/index.d.ts',
scripts: {
build: 'tsc && node scripts/build.js',
'build:watch': 'tsc --watch',
test: 'vitest',
'test:coverage': 'vitest --coverage',
storybook: 'start-storybook',
publish: 'npm publish --access public',
},
dependencies: {
react: '^18.0.0',
},
devDependencies: {
'@types/react': '^18.0.0',
typescript: '^5.0.0',
vitest: '^1.0.0',
},
};
fs.writeFileSync(
path.join(dir, 'package.json'),
JSON.stringify(packageJson, null, 2)
);
}
}
// 2. 质量检测工作流
class QualityWorkflow {
async checkAll(options: QualityOptions) {
const results = {
eslint: await this.runEslint(),
typescript: await this.runTypeCheck(),
test: await this.runTests(),
coverage: await this.checkCoverage(),
};
// 生成报告
await this.generateReport(results);
// 判断是否通过
const passed = this.evaluateResults(results);
if (!passed && options.failOnError) {
process.exit(1);
}
return results;
}
private async runEslint() {
try {
execSync('npx eslint src --format json', { stdio: 'pipe' });
return { passed: true, errors: 0 };
} catch (error) {
const output = error.stdout?.toString() || error.stderr?.toString() || '';
const result = JSON.parse(output);
return { passed: false, errors: result.length };
}
}
private async runTypeCheck() {
try {
execSync('npx tsc --noEmit', { stdio: 'pipe' });
return { passed: true, errors: 0 };
} catch {
return { passed: false, errors: -1 };
}
}
private async runTests() {
try {
execSync('npx vitest --reporter=json --outputFile=test-results.json', {
stdio: 'pipe',
});
const results = JSON.parse(
fs.readFileSync('test-results.json', 'utf-8')
);
return { passed: true, tests: results.numTotalTests };
} catch {
return { passed: false, tests: 0 };
}
}
private async checkCoverage() {
execSync('npx vitest --coverage --coverage-reporter=json', {
stdio: 'pipe',
});
const coverage = JSON.parse(
fs.readFileSync('coverage/coverage-summary.json', 'utf-8')
);
const lines = coverage.total.lines.pct;
const functions = coverage.total.functions.pct;
return {
lines,
functions,
passed: lines >= 80 && functions >= 80,
};
}
private evaluateResults(results: any): boolean {
if (!results.eslint.passed) return false;
if (!results.typescript.passed) return false;
if (!results.test.passed) return false;
if (!results.coverage.passed) return false;
return true;
}
}
// 3. 发布工作流
class PublishWorkflow {
async publish(options: PublishOptions) {
// 1. 确认版本
const currentVersion = this.getCurrentVersion();
const newVersion = this.promptVersion(currentVersion);
// 2. 更新 CHANGELOG
await this.updateChangelog(newVersion);
// 3. 运行质量检测
const quality = new QualityWorkflow();
const results = await quality.checkAll({ failOnError: true });
if (!results.eslint.passed) {
throw new Error('ESLint 检查未通过,请修复后再发布');
}
// 4. 构建
execSync('pnpm build', { stdio: 'inherit' });
// 5. 发布到 NPM
if (options.publishToNpm) {
execSync('npm publish --access public', { stdio: 'inherit' });
}
// 6. 发布到内部 CDN
if (options.publishToCdn) {
await this.publishToCDN();
}
// 7. 打 Git Tag
execSync(`git tag v${newVersion} && git push origin v${newVersion}`);
console.log(`✅ 发布成功!版本:v${newVersion}`);
}
private async publishToCDN() {
const files = fs.readdirSync('dist');
const cdnClient = new CDNClient({
endpoint: 'https://cdn.alibaba.com',
bucket: 'alife-components',
});
for (const file of files) {
await cdnClient.upload(
path.join('dist', file),
`components/${toKebabCase(process.env.npm_package_name)}/${file}`
);
}
}
}
// 4. CLI 集成
export function registerCPCommands(program: Command) {
// 组件开发
program
.command('component:create')
.argument('<name>', '组件名称')
.option('-t, --template <template>', '模板类型')
.option('--with-test', '包含测试')
.option('--storybook', '启用 Storybook')
.action((name, options) => {
const workflow = new ComponentWorkflow();
workflow.createComponent(name, options);
});
// 质量检测
program
.command('quality:check')
.option('--fail-on-error', '检查失败时退出')
.action((options) => {
const workflow = new QualityWorkflow();
workflow.checkAll(options);
});
// 发布
program
.command('publish')
.option('--npm', '发布到 NPM')
.option('--cdn', '发布到 CDN')
.action((options) => {
const workflow = new PublishWorkflow();
workflow.publish(options);
});
}
// 效果:
// - 新项目创建时间:2 天 → 5 分钟
// - 组件开发到发布周期:1 周 → 1 天
// - 代码规范检查覆盖率:60% → 100%
// create-react-app-pro 核心逻辑
const { execSync } = require('child_process');
const prompts = require('prompts');
const fs = require('fs-extra');
async function create(projectName, options) {
const answers = await prompts([
{
type: 'select',
name: 'template',
message: '选择项目模板',
choices: [
{ title: 'React + TypeScript (SPA)', value: 'react-ts' },
{ title: '中后台管理', value: 'react-admin' },
{ title: '移动端 H5', value: 'react-mobile' },
{ title: 'Monorepo (pnpm + Turborepo)', value: 'react-monorepo' },
],
},
{
type: 'multiselect',
name: 'features',
message: '选择功能模块',
choices: [
{ title: 'Zustand', value: 'zustand' },
{ title: 'React Query', value: 'query' },
{ title: 'Ant Design', value: 'antd' },
{ title: 'Tailwind CSS', value: 'tailwind' },
{ title: 'Vitest', value: 'vitest' },
],
},
]);
// 从模板仓库拉取
execSync(
`git clone https://github.com/company/react-templates.git ${projectName} --depth 1`
);
// 根据用户选择,安装对应依赖
// 注入自定义配置...
}
4.1.2 CLI 命令体系
# 脚手架命令
react-cli create <project-name> # 创建项目
react-cli add <feature> # 添加功能模块
react-cli gen <type> <name> # 代码生成(component/page/hook/api)
react-cli build # 构建
react-cli deploy <env> # 部署
react-cli lint # 代码检查
react-cli test # 运行测试
4.2 构建体系
4.2.1 多环境配置方案
// config/env.ts
const envConfig = {
development: {
API_BASE_URL: 'http://localhost:8080/api',
CDN_URL: '/',
MOCK_ENABLED: true,
SENTRY_DSN: '',
ANALYTICS_ID: '',
},
test: {
API_BASE_URL: 'https://test-api.example.com',
CDN_URL: 'https://test-cdn.example.com',
MOCK_ENABLED: false,
SENTRY_DSN: 'https://test@sentry.io/xxx',
},
staging: {
API_BASE_URL: 'https://staging-api.example.com',
CDN_URL: 'https://staging-cdn.example.com',
MOCK_ENABLED: false,
SENTRY_DSN: 'https://staging@sentry.io/xxx',
},
production: {
API_BASE_URL: 'https://api.example.com',
CDN_URL: 'https://cdn.example.com',
MOCK_ENABLED: false,
SENTRY_DSN: 'https://prod@sentry.io/xxx',
ANALYTICS_ID: 'UA-XXXXXX',
},
};
export const ENV = envConfig[import.meta.env.VITE_APP_ENV || 'development'];
4.2.2 缓存策略(文件指纹 + HTTP 缓存)
// 构建产物命名策略
// /assets/main.a1b2c3d4.js → 内容变化则 hash 变化 → 永久缓存
// /assets/vendor.e5f6g7h8.js → 依赖升级才变 → 极长缓存
// Nginx 缓存配置
location /assets/ {
expires 365d;
add_header Cache-Control "public, immutable";
}
// HTML 不缓存(确保及时加载最新资源)
location / {
expires -1;
add_header Cache-Control "no-cache, must-revalidate";
}
4.3 部署体系(CI/CD)
4.3.1 CI/CD 流水线设计
开发分支 push → 触发 CI Pipeline
├── install (pnpm install --frozen-lockfile)
├── lint (eslint, prettier, stylelint)
├── typecheck (tsc --noEmit)
├── test (vitest --coverage)
├── build (vite build)
└── notify (企业微信/飞书通知)
测试环境自动部署 (develop 分支)
├── docker build
├── push to registry
└── deploy to k8s test namespace
生产环境部署 (main 分支)
├── docker build
├── push to registry
└── canary deploy (灰度发布)
├── 10% → 观察 30min
├── 50% → 观察 30min
└── 100% → 全量发布
# .github/workflows/deploy.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm lint
- name: Type Check
run: pnpm typecheck
- name: Test
run: pnpm test --coverage
- name: Build
run: pnpm build
- name: Performance Audit
if: github.ref == 'refs/heads/main'
run: pnpm lighthouse:ci
deploy:
needs: build-and-test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Build Docker Image
run: docker build -t ${{ secrets.REGISTRY }}/app:${{ github.sha }} .
- name: Push to Registry
run: docker push ${{ secrets.REGISTRY }}/app:${{ github.sha }}
- name: Deploy to K8s (Canary)
run: |
kubectl set image deployment/app canary=${{ secrets.REGISTRY }}/app:${{ github.sha }} -n production
4.3.2 灰度发布策略
# K8s Canary Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-canary
spec:
replicas: 1 # 灰度 10%(稳定版 9 个副本)
selector:
matchLabels:
app: myapp
version: canary
template:
metadata:
labels:
app: myapp
version: canary
spec:
containers:
- name: app
image: registry/app:canary
// 前端灰度路由(通过 Header/Cookie 控制)
function getDeployVersion() {
// 1. Cookie 标记
const cookie = document.cookie.match(/version=(canary|stable)/);
if (cookie) return cookie[1];
// 2. 用户 ID 哈希(10% 用户走灰度)
const userId = getUserId();
const hash = userId.split('').reduce((a, c) => a + c.charCodeAt(0), 0);
if (hash % 10 === 0) return 'canary';
return 'stable';
}
4.3.3 Docker 部署配置
# Dockerfile - 多阶段构建
# Stage 1: 构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
# Stage 2: 运行(仅保留必要文件)
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
# 注入运行时配置(通过环境变量替换)
COPY nginx.conf /etc/nginx/templates/default.conf.template
ENV API_BASE_URL=https://api.example.com
ENV CDN_HOST=https://cdn.example.com
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
4.4 规范体系
4.4.1 代码规范金字塔
┌─────────────────────────────────────────┐
│ 自动化拦截(CI 阻断) ← 最严格 │
│ ├── ESLint error 阻断 │
│ ├── TypeScript 编译错误阻断 │
│ └── Lighthouse 性能低于 80 分阻断 │
├─────────────────────────────────────────┤
│ 自动化修复 │
│ ├── Prettier 自动格式化 │
│ ├── ESLint --fix 自动修复 │
│ └── import 自动排序 │
├─────────────────────────────────────────┤
│ 人工检查 │
│ ├── Code Review(PR 必须 2 人 approve) │
│ └── 架构评审会 │
└─────────────────────────────────────────┘
4.4.2 文档规范体系
// 1. 技术方案模板(RFC)
/**
* RFC: 新功能技术方案
*
* 背景:为什么需要这个功能?
* 目标:要达成什么效果?
* 方案设计:
* - 架构图
* - 核心流程
* - 接口设计
* - 数据库设计
* 风险与权衡:有哪些风险?有哪些替代方案被否决了?
* 排期与里程碑
*/
// 2. API 文档规范(JSDoc → 自动生成 API 文档)
/**
* 获取用户列表
* @param params.page - 页码
* @param params.pageSize - 每页数量
* @returns 分页用户列表
* @throws {AuthError} 未授权
*/
async function getUsers(params: { page: number; pageSize: number }) {
// ...
}
// 3. 组件文档规范
// 使用 Storybook 自动生成
4.4.3 研发流程规范
需求评审 → 技术方案 → 开发 → 自测 → 提测 → 发布 → 线上验证
每个阶段的 CheckList:
├── 需求评审:PRD 可测试?验收标准明确?
├── 技术方案:方案评审通过?数据库变更已审批?
├── 开发:分支从 develop 拉出?Commit 规范?
├── 自测:正反用例全覆盖?边界条件测试?
├── 提测:冒烟测试通过?部署文档已更新?
├── 发布:灰度发布观察?回滚方案已准备?
└── 线上验证:关键指标正常?用户反馈无异常?
五、技术选型 & 技术治理
5.1 技术栈选型决策
5.1.1 决策框架:TRIZ 选型法
选型四维度(每个维度 1-5 分):
1. 技术匹配度 (Technical Fit)
- 是否解决当前核心问题?
- 与现有技术栈兼容性?
2. 社区成熟度 (Community)
- GitHub Stars / Issues 响应速度
- 是否有大厂背书?
- 文档质量如何?
3. 团队能力 (Team Capability)
- 团队成员学习成本?
- 是否有外部专家?
4. 长期演进 (Longevity)
- 是否有持续维护计划?
- 项目是否活跃?
总分 ≥ 14 分 → 推荐引入
总分 10-13 → 谨慎评估
总分 < 10 → 不推荐
5.1.2 React 生态选型矩阵(2025-2026)
| 领域 | 推荐方案 | 备选方案 | 已不推荐 |
|---|---|---|---|
| 状态管理 | Zustand | Jotai / Redux Toolkit | MobX |
| 数据请求 | TanStack Query | SWR | Redux Saga |
| 路由 | React Router v6 | TanStack Router | Reach Router |
| 样式 | Tailwind CSS | CSS Modules | Styled Components |
| 构建 | Vite | - | CRA / Webpack |
| 测试 | Vitest + Testing Library | Playwright | Jest (逐渐过时) |
| SSR | Next.js App Router | Remix | Gatsby |
| 微前端 | wujie / qiankun | Module Federation | single-spa |
| 图表 | ECharts | Recharts | Chart.js |
5.1.3 React 版本升级方案
// React 17 → 18 升级路线图
// 1. 升级前检查
// 检查向后不兼容的变更:
// - Automatic Batching 行为变化
// - createRoot 替代 ReactDOM.render
// - 移除事件池
// 2. 阶段式升级
// Phase 1: 升级依赖但保持旧 API
ReactDOM.render(<App />, root); // 暂时保留,逐步迁移
// Phase 2: 迁移到新 API
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root')!);
root.render(<App />);
// Phase 3: 启用 Concurrent Features(按需)
// - useTransition / useDeferredValue
// - Suspense for data fetching
// 3. 回归测试检查清单
// - 检查所有 form 提交(Automatic Batching 影响)
// - 检查 setTimeout/setInterval(事件池移除影响)
// - 检查 useEffect 清理函数(Strict Mode 双重调用)
5.2 技术债务治理
5.2.1 技术债务分级
Level 1 - 紧急(1 周内修复):
├── 安全漏洞
├── 生产环境 Bug
├── 性能严重下降
└── 关键路径代码混乱
Level 2 - 重要(本迭代修复):
├── 不符合团队编码规范
├── 缺少必要的单元测试
├── 代码重复 > 3 处
└── 过时的 API 调用
Level 3 - 一般(技术债清理迭代):
├── 组件过度复杂(>300 行)
├── Props Drilling 过深(>3 层)
├── 缺少类型定义
└── 日志/注释不清晰
Level 4 - 可接受:
├── 不影响功能的小优化
├── 旧的但稳定的代码
└── 实验性代码(预期会重写)
5.2.2 老旧项目重构策略
策略 1:绞杀者模式(Strangler Fig)
├── 新功能用新架构开发
├── 旧功能逐步迁移
└── 最终完全替换
策略 2:增量重构
├── 先从最独立的模块开始
├── 每次只重构一个功能点
├── 每次都有完整的测试覆盖
└── 不改变外部接口
策略 3:限制变更范围
├── 不要一次改太多
├── PR 控制在 400 行以内
├── 每次重构 1-2 个组件
└── 保持新旧代码共存期
5.3 版本管理与依赖治理
5.3.1 依赖管理策略
// package.json
{
"dependencies": {
"react": "^18.3.0", // ^ 允许次版本升级
"antd": "~5.15.0", // ~ 仅允许补丁升级
"zustand": "4.5.0", // 锁定精确版本(核心库)
"lodash-es": "4.17.21" // 精确锁定
},
"overrides": {
"react": "18.3.0", // 强制所有子依赖使用统一版本
"typescript": "5.4.0"
}
}
5.3.2 依赖审计与更新流程
# 1. 安全检查(日常)
pnpm audit
# 2. 过期检查(每月)
pnpm outdated
# 3. 依赖更新(每月)
pnpm update --interactive # 交互式选择更新
# 4. 检查 Breaking Changes
# 查看 CHANGELOG 和迁移指南
# 5. 更新后运行全部测试
pnpm test
pnpm typecheck
pnpm lint
# 6. 提交依赖锁文件
git commit -m "chore(deps): upgrade dependencies"
六、数据 & 网络 & 安全架构
6.1 数据层架构
6.1.1 GraphQL + Apollo Client
// Apollo Client 配置
import { ApolloClient, InMemoryCache, HttpLink, from } from '@apollo/client';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ message, extensions }) => {
if (extensions?.code === 'UNAUTHENTICATED') {
// token 过期,刷新或跳转登录
}
});
}
});
const retryLink = new RetryLink({
delay: { initial: 300, max: 5000, jitter: true },
attempts: { max: 3 },
});
const client = new ApolloClient({
link: from([errorLink, retryLink, new HttpLink({ uri: '/graphql' })]),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
users: {
// 合并分页数据
keyArgs: ['filters'],
merge(existing, incoming) {
return {
...incoming,
items: [...(existing?.items || []), ...incoming.items],
};
},
},
},
},
},
}),
});
// GraphQL Query
const GET_USERS = gql`
query GetUsers($page: Int!, $pageSize: Int!, $filter: UserFilter) {
users(page: $page, pageSize: $pageSize, filter: $filter) {
items { id name email role }
totalCount hasNextPage
}
}
`;
function UserList() {
const { data, loading, fetchMore } = useQuery(GET_USERS, {
variables: { page: 1, pageSize: 20 },
});
const loadMore = () => {
fetchMore({
variables: { page: data.users.items.length / 20 + 1, pageSize: 20 },
});
};
}
6.1.2 请求封装:统一错误处理 + 重试 + 日志
// api/client.ts
import axios, { AxiosError, AxiosRequestConfig } from 'axios';
const client = axios.create({
baseURL: ENV.API_BASE_URL,
timeout: 15000,
});
// 请求拦截器
client.interceptors.request.use(config => {
const token = getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 添加请求 ID(全链路追踪)
config.headers['X-Request-ID'] = generateRequestId();
return config;
});
// 响应拦截器(全局错误处理)
client.interceptors.response.use(
response => response.data,
async (error: AxiosError) => {
const { config, response } = error;
// Token 过期 → 自动刷新
if (response?.status === 401) {
try {
const newToken = await refreshToken();
if (config) {
config.headers.Authorization = `Bearer ${newToken}`;
return client(config); // 重试
}
} catch {
redirectToLogin();
}
}
// 网络超时 → 重试
if (error.code === 'ECONNABORTED' && config) {
if (!config._retryCount) config._retryCount = 0;
if (config._retryCount < 3) {
config._retryCount++;
return client(config);
}
}
// 统一上报错误
reportError(error);
return Promise.reject(error);
}
);
6.2 安全防护体系
6.2.1 XSS 防护
// XSS 防护三原则:输入验证 + 输出编码 + CSP
// 1. 输入验证(前端第一道防线)
function sanitizeInput(input: string): string {
return input
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\//g, '/');
}
// 2. React 默认转义(JSX 自动编码)
// <div>{userInput}</div> → ✅ 自动编码
// 3. dangerouslySetInnerHTML 必须配合 DOMPurify
import DOMPurify from 'dompurify';
function SafeHTML({ html }: { html: string }) {
const cleanHTML = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href', 'target'],
});
return <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />;
}
// 4. CSP(Content Security Policy)HTTP 头
// Content-Security-Policy:
// default-src 'self';
// script-src 'self' 'wasm-unsafe-eval';
// style-src 'self' 'unsafe-inline';
// img-src 'self' https://cdn.example.com data:;
// connect-src 'self' https://api.example.com;
// font-src 'self';
🎯 某电商平台 XSS 漏洞安全事件处理案例
/**
* 【某电商平台 XSS 漏洞安全事件处理案例】
*
* 事件背景:
* - 2024 年 3 月,安全团队发现商品评论处存在 XSS 漏洞
* - 攻击者可注入恶意脚本,窃取用户 Cookie 和订单信息
* - 影响范围:所有使用评论功能的用户(约 500 万)
*
* 事件处理过程:
* 1. 发现与上报(Day 1)
* 2. 紧急修复(Day 1-2)
* 3. 全站排查(Day 3-7)
* 4. 安全审计(Day 8-14)
* 5. 安全加固(Day 15-30)
*/
import DOMPurify from 'dompurify';
import { marked } from 'marked';
// 1. 紧急修复:输入过滤
function sanitizeUserInput(input: string): string {
if (!input) return '';
return DOMPurify.sanitize(input, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br'],
ALLOWED_ATTR: [],
});
}
// 2. 紧急修复:富文本内容过滤
function sanitizeRichContent(content: string): string {
if (!content) return '';
// 使用 marked 解析 Markdown
const html = marked.parse(content, { async: false }) as string;
// 严格过滤 HTML
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: [
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'p', 'br', 'hr',
'ul', 'ol', 'li',
'blockquote', 'pre', 'code',
'strong', 'em', 'del', 'ins',
'a', 'img',
],
ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class'],
ALLOW_DATA_ATTR: false,
ADD_ATTR: ['target'],
});
}
// 3. URL 验证
function validateUrl(url: string): boolean {
try {
const parsed = new URL(url);
return ['http:', 'https:'].includes(parsed.protocol);
} catch {
return false;
}
}
// 4. CSP 配置加固
const CSP_HEADER = `
default-src 'self';
script-src 'self' 'nonce-{NONCE}' 'strict-dynamic';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https://img.example.com;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
`.replace(/\s+/g, ' ').trim();
// 5. 安全监控告警
class SecurityMonitor {
private suspiciousPatterns = [
/<script/i,
/javascript:/i,
/on\w+\s*=/i,
/<iframe/i,
/<object/i,
/<embed/i,
];
logSecurityEvent(event: SecurityEvent) {
// 上报到安全监控系统
fetch('/api/security/events', {
method: 'POST',
body: JSON.stringify({
...event,
timestamp: Date.now(),
userAgent: navigator.userAgent,
url: window.location.href,
}),
});
// 高危事件立即告警
if (event.severity === 'critical') {
this.sendAlert(event);
}
}
checkXSS(input: string): boolean {
return this.suspiciousPatterns.some(pattern => pattern.test(input));
}
private sendAlert(event: SecurityEvent) {
// 发送到安全告警渠道(企业微信/飞书)
fetch('/api/security/alert', {
method: 'POST',
body: JSON.stringify({
type: 'XSS_DETECTED',
event,
}),
});
}
}
// 6. 事件处理后的安全加固
// 实施后效果:
// - XSS 漏洞修复率:100%
// - 安全事件响应时间:24h → 2h
// - 安全告警准确率:95%+
// - 年度安全事件:50+ → 5
🎯 某社交平台 CSRF 攻击防护实战案例
/**
* 【某社交平台 CSRF 攻击防护实战案例】
*
* 攻击背景:
* - 攻击者诱导已登录用户访问恶意页面
* - 利用用户的登录状态,自动发起转账/修改密码等操作
* - 攻击成功率高达 30%
*
* 防护方案:
* - SameSite Cookie
* - CSRF Token(双重提交 Cookie 模式)
* - 验证码/密码确认
* - 请求头验证
*/
// 1. CSRF Token 生成与验证
class CSRFProtection {
private tokenName = '_csrf_token';
private headerName = 'X-CSRF-Token';
generateToken(): string {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
setTokenCookie(token: string): void {
document.cookie = `${this.tokenName}=${token}; SameSite=Strict; Secure; HttpOnly; path=/`;
}
getTokenFromCookie(): string | null {
const match = document.cookie.match(new RegExp(`(^| )${this.tokenName}=([^;]+)`));
return match ? match[2] : null;
}
// 验证请求头中的 Token
validateToken(requestToken: string): boolean {
const cookieToken = this.getTokenFromCookie();
return requestToken === cookieToken && cookieToken !== null;
}
}
const csrfProtection = new CSRFProtection();
// 2. 请求拦截器中注入 Token
axios.interceptors.request.use(config => {
const csrfToken = csrfProtection.getTokenFromCookie();
if (csrfToken) {
config.headers[csrfProtection.headerName] = csrfToken;
}
return config;
});
// 3. 敏感操作二次验证
function useSensitiveOperation() {
const [requiresVerification, setRequiresVerification] = useState(false);
const [verificationType, setVerificationType] = useState<'password' | 'sms' | null>(null);
const requestVerification = (type: 'password' | 'sms') => {
setVerificationType(type);
setRequiresVerification(true);
};
const confirmVerification = async (code: string) => {
if (verificationType === 'sms') {
const response = await api.verifySmsCode(code);
if (response.valid) {
setRequiresVerification(false);
return true;
}
} else if (verificationType === 'password') {
const response = await api.verifyPassword(code);
if (response.valid) {
setRequiresVerification(false);
return true;
}
}
return false;
};
return { requiresVerification, requestVerification, confirmVerification };
}
// 4. 防护效果统计
// 实施 CSRF 防护后:
// - CSRF 攻击成功率:30% → 0%
// - 安全事件月均数量:15 → 0
// - 用户投诉:无
6.2.2 CSRF 防护 + Token 安全
// CSRF 防护
// 1. SameSite Cookie
// Set-Cookie: session=xxx; SameSite=Lax; Secure; HttpOnly
// 2. CSRF Token(双重提交 Cookie 模式)
function setCSRFToken() {
const token = generateToken();
document.cookie = `csrf_token=${token}; SameSite=Strict; Secure`;
return token;
}
// 3. 自定义请求头验证(更安全)
axios.interceptors.request.use(config => {
// 后端验证 X-CSRF-Token 头
config.headers['X-CSRF-Token'] = getCSRFToken();
return config;
});
// Token 安全存储
// ❌ localStorage → 容易被 XSS 窃取
// ✅ httpOnly Cookie → JS 无法读取,最安全
// JWT 拆分存储(进阶)
// Access Token(短期):httpOnly Cookie
// Refresh Token(长期):httpOnly Cookie + path 限制
// Set-Cookie: access_token=xxx; HttpOnly; Secure; SameSite=Strict; Max-Age=900
// Set-Cookie: refresh_token=xxx; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh
6.2.3 敏感数据加密
// 前端敏感数据加密(Web Crypto API)
async function encrypt(plaintext: string, password: string): Promise<string> {
const enc = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
'raw', enc.encode(password), 'PBKDF2', false, ['deriveKey']
);
const key = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt: crypto.getRandomValues(new Uint8Array(16)),
iterations: 100000, hash: 'SHA-256' },
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt']
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
enc.encode(plaintext)
);
return JSON.stringify({
iv: Array.from(iv),
data: Array.from(new Uint8Array(encrypted)),
});
}
🎯 某金融平台数据安全合规改造案例
/**
* 【某金融平台数据安全合规改造案例】
*
* 背景:
* - 金融行业,合规要求极高(等保三级、PCI-DSS)
* - 需要对敏感数据进行加密存储和传输
* - 用户身份证号、银行卡号、交易密码等必须加密
*
* 合规要求:
* 1. 敏感数据加密存储(前端也要加密)
* 2. 传输链路加密(HTTPS + TLS 1.3)
* 3. 密钥管理(不能在前端硬编码)
* 4. 操作日志(所有敏感操作必须审计)
*/
import { crypto } from 'crypto';
// 1. 敏感数据加密 SDK
class SensitiveDataEncryptor {
private publicKey: CryptoKey | null = null;
private readonly KEY_ID = 'sensitive-data-key-v1';
async initialize() {
// 从密钥服务获取公钥
const keyData = await fetch('/api/keys/sensitive-data', {
headers: { 'X-Request-ID': generateRequestId() },
}).then(r => r.json());
this.publicKey = await crypto.subtle.importKey(
'spki',
Uint8Array.from(atob(keyData.publicKey), c => c.charCodeAt(0)),
{ name: 'RSA-OAEP', hash: 'SHA-256' },
false,
['encrypt']
);
}
// 加密敏感数据
async encrypt(data: string): Promise<string> {
if (!this.publicKey) {
await this.initialize();
}
// 生成随机 AES 密钥
const aesKey = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt']
);
// 使用 RSA 公钥加密 AES 密钥
const iv = crypto.getRandomValues(new Uint8Array(12));
const encryptedAesKey = await crypto.subtle.encrypt(
{ name: 'RSA-OAEP' },
this.publicKey!,
await crypto.subtle.exportKey('raw', aesKey)
);
// 使用 AES 加密数据
const encryptedData = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
aesKey,
new TextEncoder().encode(data)
);
// 返回加密后的数据(包含加密的 AES 密钥)
return JSON.stringify({
version: this.KEY_ID,
algorithm: 'RSA-OAEP+AES-GCM',
iv: Array.from(iv),
encryptedKey: Array.from(new Uint8Array(encryptedAesKey)),
encryptedData: Array.from(new Uint8Array(encryptedData)),
});
}
}
// 2. 身份证号脱敏
function maskIdCard(idCard: string): string {
if (!idCard || idCard.length !== 18) return idCard;
return `${idCard.slice(0, 6)}********${idCard.slice(-4)}`;
}
// 3. 银行卡号脱敏
function maskBankCard(bankCard: string): string {
if (!bankCard || bankCard.length < 12) return bankCard;
return `${bankCard.slice(0, 4)} **** **** ${bankCard.slice(-4)}`;
}
// 4. 手机号脱敏
function maskPhone(phone: string): string {
if (!phone || phone.length !== 11) return phone;
return `${phone.slice(0, 3)}****${phone.slice(-4)}`;
}
// 5. 敏感操作日志
class SensitiveOperationLogger {
log(operation: string, data: SensitiveOperationData) {
const logEntry = {
timestamp: new Date().toISOString(),
userId: getCurrentUserId(),
operation,
ip: getClientIP(),
userAgent: navigator.userAgent,
// 敏感数据脱敏后记录
dataMasked: this.maskSensitiveData(data),
};
// 发送到日志服务
fetch('/api/security/audit-log', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Request-ID': generateRequestId(),
},
body: JSON.stringify(logEntry),
});
}
private maskSensitiveData(data: any): any {
const mask = (value: string) => {
if (!value) return value;
if (value.length <= 8) return '****';
return `${value.slice(0, 4)}****${value.slice(-4)}`;
};
return {
idCard: mask(data.idCard),
bankCard: mask(data.bankCard),
phone: mask(data.phone),
// 其他字段正常记录
...data,
};
}
}
// 6. 安全组件:敏感信息展示
function SensitiveDisplay({ value, type }: { value: string; type: 'idCard' | 'bankCard' | 'phone' | 'custom' }) {
const [revealed, setRevealed] = useState(false);
const { requiresVerification, requestVerification, confirmVerification } = useSensitiveOperation();
const handleReveal = async () => {
if (!revealed) {
// 敏感操作需要二次验证
requestVerification('password');
} else {
setRevealed(false);
}
};
const getMaskedValue = () => {
switch (type) {
case 'idCard': return maskIdCard(value);
case 'bankCard': return maskBankCard(value);
case 'phone': return maskPhone(value);
default: return value.slice(0, 4) + '****' + value.slice(-4);
}
};
return (
<div className="sensitive-display">
<span className="value">{revealed ? value : getMaskedValue()}</span>
<button onClick={handleReveal}>
{revealed ? '隐藏' : '查看'}
</button>
{requiresVerification && (
<VerificationModal
type={verificationType}
onVerify={confirmVerification}
onCancel={() => setRequiresVerification(false)}
/>
)}
</div>
);
}
// 效果:
// - 合规审计通过率:70% → 100%
// - 敏感数据泄露事件:5 → 0
// - 安全告警响应时间:4h → 15min
6.3 稳定性监控与容灾
6.3.1 Sentry 错误监控集成
import * as Sentry from '@sentry/react';
Sentry.init({
dsn: ENV.SENTRY_DSN,
// 环境区分
environment: import.meta.env.VITE_APP_ENV,
// 采样率(生产 10% 以减少费用)
tracesSampleRate: ENV.isProd ? 0.1 : 1.0,
replaysSessionSampleRate: ENV.isProd ? 0.01 : 1.0,
// Release 标记(关联 Git 版本)
release: import.meta.env.VITE_APP_VERSION,
beforeSend(event, hint) {
// 过滤已知的非关键错误
const error = hint.originalException;
if (error instanceof NetworkError && event.message?.includes('timeout')) {
return null; // 忽略网络超时错误
}
return event;
},
});
// 错误边界集成
function App() {
return (
<Sentry.ErrorBoundary fallback={<ErrorPage />}>
<RouterProvider router={router} />
</Sentry.ErrorBoundary>
);
}
// 手动上报
Sentry.captureException(new Error('Custom error'), {
tags: { module: 'payment' },
extra: { userId: '123' },
});
6.3.2 容灾方案与降级策略
// 降级策略
const DEGRADE_STRATEGY = {
// 1. 功能降级(API 不可用时,使用本地缓存)
featureDegrade: (api: () => Promise<T>, cache: () => T | null) => {
return async (): Promise<T> => {
try {
return await api();
} catch {
const cached = cache();
if (cached) {
console.warn('API failed, using cache');
return cached;
}
throw new Error('Service unavailable');
}
};
},
// 2. 组件降级(CDN 不可用时)
componentDegrade: (primary: LazyComponent, fallback: LazyComponent) => {
return lazy(() =>
primary().catch(() => {
console.warn('Primary component failed, using fallback');
return fallback();
})
);
},
// 3. 服务降级(重试失败后)
serviceDegrade: async (request: () => Promise<T>, fallback: () => T) => {
try {
return await retry(request, { times: 3 });
} catch {
console.warn('Service degraded');
return fallback();
}
},
};
// 全局错误边界
class GlobalErrorBoundary extends React.Component<
{ children: React.ReactNode },
{ hasError: boolean }
> {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
Sentry.captureException(error, { contexts: { react: info } });
}
render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<h1>页面出了点问题</h1>
<p>请刷新重试,或联系技术支持</p>
<button onClick={() => window.location.reload()}>刷新页面</button>
<a href="/">返回首页</a>
</div>
);
}
return this.props.children;
}
}
七、团队赋能 & 技术管理
7.1 组件生态建设
7.1.1 Storybook 组件文档体系
// packages/ui/src/Button/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'UI/Button',
component: Button,
args: {
children: '按钮文本',
variant: 'primary',
size: 'md',
disabled: false,
},
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'danger', 'ghost'],
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
},
onClick: { action: 'clicked' },
},
};
export default meta;
type Story = StoryObj<typeof meta>;
// 用例 1:基础按钮
export const Primary: Story = { args: { variant: 'primary' } };
// 用例 2:Loading 状态
export const Loading: Story = {
args: { loading: true, children: '提交中' },
};
// 用例 3:交互式自动测试
export const Clickable: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button');
await userEvent.click(button);
// 验证 onClick 被调用
},
};
7.1.2 组件库维护策略
版本管理:
├── 语义化版本(SemVer)
│ ├── Major: 不兼容的 API 变更
│ ├── Minor: 向后兼容的功能新增
│ └── Patch: 向后兼容的 Bug 修复
├── Changeset 管理(自动生成 CHANGELOG)
质量保障:
├── 每个组件最少 3 个测试用例
├── 每个组件有 Storybook 文档
├── TypeScript 类型导出
├── A11y 审计通过
└── Bundle Size 监控(小于阈值)
发布流程:
├── MR → Code Review
├── Changeset → CHANGELOG
├── CI 通过
└── npm publish
7.2 技术输出与知识传承
7.2.1 技术方案模板(RFC)
# RFC: [功能名称]
## 背景与动机
为什么需要这个功能?解决了什么痛点?
## 方案设计
### 整体架构
(架构图 + 核心流程说明)
### 前端方案
- 核心组件设计
- 状态管理方案
- 路由设计
### 接口设计
- API 列表
- 请求/响应格式
## 风险与应对
| 风险 | 影响 | 概率 | 应对措施 |
|------|------|------|----------|
| xxx | xxx | xxx | xxx |
## 替代方案
为什么这些方案被否决?
## 排期
| 里程碑 | 日期 | 交付物 |
|--------|------|--------|
7.2.2 代码评审 (CR) 清单
功能检查:
├── 实现了预期的功能?
├── 处理了边界情况?(空值、异常输入)
代码质量:
├── 命名是否清晰表达意图?
├── 函数是否遵循单一职责?
├── 消除了重复代码?
性能检查:
├── 是否有不必要的重渲染?
├── 大列表是否用了虚拟滚动?
├── 图片是否懒加载?
安全审查:
├── 是否有 XSS 风险?(dangerouslySetInnerHTML)
├── 敏感数据是否明文存储?
├── 用户输入是否校验和清洗?
可维护性:
├── 组件是否足够独立?
├── 是否有必要的类型定义?
├── 是否有必要的单元测试?
7.2.3 新人培训体系
Week 1: 环境搭建 + 基础文档
├── IDE & 工具链配置
├── 阅读 Coding Standards
├── 阅读基础层文档
└── 完成 Todo List Demo
Week 2-3: 业务入门
├── 阅读本项目 RFC 文档
├── 完成 Bug Fix PR
├── 参与 Code Review
└── 阅读进阶层文档
Week 4-6: 独立开发
├── 独立完成 1 个 Feature
├── 了解 CI/CD 流程
└── 参与日常站会
Month 2-3: 深入参与
├── 参与技术方案设计
├── 负责一个模块的 Owner
└── 阅读精通层文档
7.3 研发效率提升
7.3.1 低代码平台设计
// Schema 驱动的页面 = JSON 配置 → 组件解析器 → 页面
interface PageSchema {
layout: 'grid' | 'flex' | 'tabs';
sections: PageSection[];
dataSources: DataSource[];
}
interface PageSection {
id: string;
type: 'table' | 'form' | 'chart' | 'cards';
config: TableConfig | FormConfig | ChartConfig;
dataSourceKey: string; // 关联数据
permissions?: string[];
}
// 页面渲染引擎
function SchemaRenderer({ schema }: { schema: PageSchema }) {
// 数据聚合
const dataSources = useDataSources(schema.dataSources);
// 权限过滤(隐藏无权限的 section)
const visibleSections = schema.sections.filter(s =>
s.permissions?.every(p => hasPermission(p)) ?? true
);
return (
<Layout type={schema.layout}>
{visibleSections.map(section => (
<SectionRenderer
key={section.id}
type={section.type}
config={section.config}
data={dataSources[section.dataSourceKey]}
/>
))}
</Layout>
);
}
7.3.2 代码生成器
# 脚手架 codegen 命令
react-cli gen component UserProfile
# 生成:
# - UserProfile/index.tsx
# - UserProfile/UserProfile.test.tsx
# - UserProfile/UserProfile.stories.tsx
# - UserProfile/UserProfile.module.css
# - UserProfile/types.ts
react-cli gen hook useProductList
# 生成:
# - hooks/useProductList.ts
# - hooks/__tests__/useProductList.test.ts
react-cli gen api Product
# 生成:
# - api/modules/product.api.ts
# - dto/product.dto.ts
八、前沿技术跟踪
8.1 React 最新特性
8.1.1 React 19 核心变化
// 1. React Server Components (RSC) 进入稳定期
// app/page.tsx (Server Component)
export default async function Page() {
const data = await fetch('https://api.example.com/data');
const json = await data.json();
return (
<div>
<h1>Server Rendered</h1>
<ClientInteractivity data={json} />
</div>
);
}
// 2. Actions (Server Actions)
// app/actions.ts
'use server';
export async function createPost(data: FormData) {
const title = data.get('title');
await db.post.create({ data: { title } });
revalidatePath('/posts');
}
// app/page.tsx
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" />
<button type="submit">Create</button>
</form>
);
}
// 3. use() Hook(读取 Promise/Context)
function UserList({ usersPromise }: { usersPromise: Promise<User[]> }) {
const users = React.use(usersPromise); // 直接 use Promise
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
// 4. useOptimistic(乐观更新)
function MessageList({ messages }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [...state, newMessage]
);
const sendMessage = async (text: string) => {
addOptimisticMessage({ text, pending: true });
await api.sendMessage(text);
};
return (
<div>
{optimisticMessages.map(msg => (
<Message key={msg.id} text={msg.text} pending={msg.pending} />
))}
<SendForm onSend={sendMessage} />
</div>
);
}
8.1.2 React Forget(自动 Memoization 编译器)
// React Forget (即将发布) → 无需手动 useMemo/useCallback
// 编译器自动分析并注入 memoization
// 今天写的代码(手动 memo):
const processedData = useMemo(() => {
return items.filter(i => i.active).map(i => ({ ...i, label: i.name }));
}, [items]);
const handleClick = useCallback(() => {
onClick(id);
}, [onClick, id]);
// React Forget 之后(编译器自动处理):
const processedData = items.filter(i => i.active).map(i => ({ ...i, label: i.name }));
const handleClick = () => onClick(id);
// ↑ 编译器自动识别依赖并注入等价的 memoization
// ↓ 生成等价的代码:
// const processedData = useMemo(..., [items]);
// const handleClick = useCallback(..., [onClick, id]);
8.2 前沿架构方案
8.2.1 RSC(React Server Components)
// Server Component:在服务端运行,无客户端 JS
// app/products/page.server.tsx
export default async function ProductList() {
const products = await db.product.findMany();
return (
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
{/* Client Component 嵌套 */}
<AddToCartButton productId={product.id} />
</li>
))}
</ul>
);
}
// Client Component:在浏览器运行
'use client';
function AddToCartButton({ productId }: { productId: string }) {
const addToCart = useCartStore(s => s.addItem);
const [loading, setLoading] = useState(false);
return (
<button
onClick={async () => {
setLoading(true);
await addToCart(productId);
setLoading(false);
}}
disabled={loading}
>
{loading ? '添加中...' : '加入购物车'}
</button>
);
}
8.2.2 Islands Architecture(岛屿架构)
传统 SSR:整个页面 hydration(交互注水)→ 所有组件需要 JS
Islands 架构:
├── 静态 HTML(海洋)
│ ├── 不需要 JS → 零 bundle
│ └── 大部分页面内容
└── 交互组件(岛屿)
├── 需要 JS → 仅加载必要代码
└── 每个岛独立 hydration
优势:
├── 首屏 HTML 立即可用
├── 非交互部分零 JS
├── 每个交互独立加载/ Hydrate
└── 显著减少首屏 JS 体积
// Island 组件(仅关键交互部分使用 JS)
import { island } from '@company/island';
const AddToCart = island(() => import('./AddToCart'), {
fallback: <Button disabled>加载中</Button>,
defer: true, // 延迟 Hydrate
intersect: true, // 进入视口才 Hydrate
});
const ProductRating = island(() => import('./ProductRating'), {
defer: true,
intersect: true,
});
8.2.3 Remix 框架(Web 标准优先)
// Remix 核心理念:基于 Web 标准的 <form> 而非 JS 驱动的 fetch
// route.tsx
import { ActionFunctionArgs, LoaderFunctionArgs, json } from '@remix-run/node';
import { Form, useLoaderData, useActionData } from '@remix-run/react';
// Server Loader(服务端数据获取)
export async function loader({ params }: LoaderFunctionArgs) {
const product = await db.product.findUnique({ where: { id: params.id } });
return json({ product });
}
// Server Action(服务端表单处理)
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const title = formData.get('title');
const review = await db.review.create({ data: { title } });
return json({ review });
}
// 客户端组件
export default function Product() {
const { product } = useLoaderData<typeof loader>();
const actionData = useActionData<typeof action>();
return (
<div>
<h1>{product.name}</h1>
<Form method="post">
<input name="title" placeholder="写评论" />
<button type="submit">提交</button>
</Form>
{actionData?.review && <p>评论成功!</p>}
</div>
);
}
附录:架构师能力模型
📊 React 架构师能力金字塔
Level 5: 技术布道者(影响行业)
├── 开源贡献
├── 技术博客 / 演讲
├── 制定团队技术战略
└── 推动前端技术演进
Level 4: 架构师(主导大型项目)
├── 微前端架构
├── 性能优化全链路
├── 工程化体系搭建
├── 安全架构设计
└── 阶段性技术 Review & 规划
Level 3: 高级工程师(独立负责模块)
├── 底层原理理解
├── 现代状态管理
├── TypeScript 工程化
├── SSR/SSG 实践
└── 技术方案设计
Level 2: 中级工程师(独立开发)
├── React 核心 API 熟练
├── 性能优化(memo/useCallback)
├── 基础路由与状态管理
├── Testing Unit Test
└── Code Review 参与
Level 1: 初级工程师(学习与模仿)
├── JSX/State/Props
├── 基础 Hooks
├── 表单处理
├── 调试技巧
└── 编码规范遵守
📚 扩展推荐
- React 源码分析:React.tech(中文社区源码分析)
- 微前端:qiankun、wujie 官方文档
- 性能监控:Lighthouse、WebPageTest
- 设计模式:《重构》《设计模式》GoF
- 架构设计:《前端架构师培养手册》
- 工程化:《Creating CI/CD Pipelines for Frontend》
🎯 进阶里程碑
入门(0-6 个月)
├── 可以独立开发业务页面
└── 掌握 React 核心 API
进阶(6-18 个月)
├── 理解 React 底层原理
├── 解决复杂业务问题
└── 掌握现代状态管理
精通(2+ 年)
├── 架构设计能力
├── 极致性能优化
├── 大型应用解决方案
└── 团队赋能与培训
总结
本文档从资深 React 架构师的角度,全面梳理了精通层的核心能力体系:
🎯 三大核心能力
- 架构设计:分层解耦、组件架构、路由架构、状态架构、可扩展性
- 大型应用:微前端(qiankun/MF)、SSR 深度实践(Next.js)、中后台权限架构
- 极致性能:运行时优化、构建优化、Web Vitals、性能监控体系
🏗️ 三大工程体系
- 工程化体系:自研脚手架、CI/CD、Docker 部署、规范体系
- 技术治理:技术选型决策框架、技术债务管理、依赖治理
- 安全架构:XSS/CSRF 防护、数据加密、Sentry 监控、容灾降级
🤝 两大软技能
- 团队赋能:Storybook 组件文档、Code Review 机制、新人培训体系
- 前沿技术:React 19 新特性、React Forget、RSC、Server Actions
版本:v1.0
最后更新:2026-05-10
前置要求:完成基础层 + 进阶层学习
适用版本:React 18+ / Next.js 14+ / Vite 5+ / TypeScript 5+
作者视角:基于 5 年+ React 大型项目架构经验 + 源码分析 + 技术管理经验
更多推荐

所有评论(0)