跳到主要内容

React 大型应用架构设计

大型 React 应用设计原则

1. Feature-Sliced Design 架构

src/
app/ # 应用层:路由、Provider、全局样式
pages/ # 页面层:组合 widgets 组成页面
widgets/ # 组件层:独立的页面区块(Header、Sidebar)
features/ # 功能层:用户操作(点赞、搜索、评论)
entities/ # 实体层:业务对象(User、Article、Order)
shared/ # 共享层:UI 组件库、工具函数、API 客户端

依赖规则:上层可引用下层,同层之间不能随意引用。

模块边界:每个 slice 只通过 index.ts 暴露公开 API。

2. 状态分层管理

服务端状态(Server State)
-> 来自 API,有缓存、失效、同步问题
-> React Query / SWR / RTK Query

客户端状态(Client State)
-> UI 状态(弹窗开关、选中项)
-> useState / useReducer / Zustand

表单状态(Form State)
-> 表单值、验证、提交
-> React Hook Form + Zod

URL 状态(URL State)
-> 搜索参数、分页、筛选条件
-> useSearchParams / nuqs 库

关键原则:不要把服务端状态放进 Redux/Zustand,React Query 专门处理异步状态。

3. 代码分割策略

// 路由级分割(必做)
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Analytics = lazy(() => import('./pages/Analytics'))

// 功能级分割(大功能模块)
const RichEditor = lazy(() => import('./features/editor/RichEditor'))

// 预加载策略(hover 时触发)
const prefetchDashboard = () => import('./pages/Dashboard')

<Link
to="/dashboard"
onMouseEnter={prefetchDashboard} // 鼠标悬停时预加载
>
Dashboard
</Link>

4. 组件设计模式

Compound Components(复合组件)

// 用于构建高度可定制的组件
const Select = ({ children, value, onChange }) => {
return (
<SelectContext.Provider value={{ value, onChange }}>
<div className="select">{children}</div>
</SelectContext.Provider>
)
}
Select.Option = ({ value, children }) => {
const { value: selected, onChange } = useSelectContext()
return (
<div
className={selected === value ? 'selected' : ''}
onClick={() => onChange(value)}
>
{children}
</div>
)
}

// 使用
<Select value={val} onChange={setVal}>
<Select.Option value="a">Option A</Select.Option>
<Select.Option value="b">Option B</Select.Option>
</Select>

Render Props / Children as Function

// 将渲染逻辑控制权交给使用者
function DataProvider({ render }) {
const [data] = useFetch('/api/data')
return render(data)
}

// 使用:自定义渲染
<DataProvider render={data => <CustomChart data={data} />} />

useImperativeHandle 暴露命令式接口

const Dialog = forwardRef((props, ref) => {
const [open, setOpen] = useState(false)

useImperativeHandle(ref, () => ({
open: () => setOpen(true),
close: () => setOpen(false),
}))

return open ? <div>{props.children}</div> : null
})

// 使用
const dialogRef = useRef(null)
<Dialog ref={dialogRef} />
<button onClick={() => dialogRef.current.open()}>打开</button>

5. Monorepo 组织

packages/
ui/ # 共享 UI 组件库(Design System)
utils/ # 工具函数
types/ # 共享 TypeScript 类型
api-client/ # API 客户端(自动生成)
config/ # 共享配置(ESLint、TS、Tailwind)
apps/
web/ # 主应用
admin/ # 管理后台
mobile/ # React Native 应用

工具链:Turborepo(增量构建缓存)+ pnpm workspaces

版本管理:内部包用 workspace:*,公共包用 Changesets

6. 测试体系

// 单元测试:纯函数、hooks、工具
test('useCounter', () => {
const { result } = renderHook(() => useCounter(0))
act(() => result.current.increment())
expect(result.current.count).toBe(1)
})

// 集成测试:组件 + API Mock(MSW)
test('登录成功后跳转首页', async () => {
server.use(
http.post('/api/login', () => HttpResponse.json({ token: 'abc' }))
)
render(<LoginPage />, { wrapper: Providers })
await userEvent.type(screen.getByLabelText('邮箱'), '[email protected]')
await userEvent.click(screen.getByRole('button', { name: '登录' }))
await expect(screen.findByText('欢迎回来')).resolves.toBeInTheDocument()
})

// E2E(Playwright):核心用户旅程
test('完整购买流程', async ({ page }) => {
await page.goto('/products')
await page.click('[data-testid=add-to-cart]')
await page.goto('/checkout')
await page.fill('[name=card]', '4242 4242 4242 4242')
await page.click('button[type=submit]')
await expect(page.getByText('订单已确认')).toBeVisible()
})

7. 资深面试题

  • 如何设计一个跨团队复用的组件库?
    • 严格的语义化版本 + Changesets 管理
    • Storybook 文档 + 视觉回归测试(Chromatic)
    • Compound Component 模式提供灵活性
    • 组件 API 向后兼容,breaking change 走大版本
  • 微前端架构中 React 应用如何共享状态?
    • CustomEvent / window.postMessage(松耦合)
    • Module Federation 共享单例 store
    • URL 作为通信载体(筛选条件、路由参数)
  • 大型 React 应用如何做权限控制?
    • 路由级:<PrivateRoute> 包裹需鉴权路由
    • 组件级:usePermission('edit:post') hook
    • 数据级:服务端返回时过滤,前端只做展示判断
    • Feature Flag:LaunchDarkly 实现灰度和 A/B 测试