Skip to content

A05 数据与状态管理:从本地状态到全局数据架构

目标:建立系统化的数据与状态管理思维,能根据业务场景设计合理的数据流、缓存策略和一致性方案。


核心要点(TL;DR)

  • 应用中的状态应该被分类管理,不能一刀切地塞进全局 Store。
  • 现代前端状态通常分为:本地 UI 状态、全局共享状态、服务端状态、URL 状态、表单状态。
  • 服务端状态管理是大型应用的核心挑战,需要处理缓存、失效、乐观更新、去重等问题。
  • React Query / SWR / TanStack Query 是服务端状态管理的事实标准。
  • 缓存策略没有银弹,需要根据数据特征(读多写少、实时性、一致性要求)选择。
  • 数据一致性、冲突解决、离线同步是高级前端架构师必须掌握的能力。

学习时长与前置知识

  • 建议学习时长:2-3 周(每周投入 6-8 小时)
  • 前置知识:React/Vue、TypeScript、状态管理基础

一、为什么数据与状态管理是架构问题?

1.1 小项目与大项目的分水岭

小项目的状态管理很简单:

js
const [count, setCount] = useState(0);

但当项目变大,状态问题会变得复杂:

  • 多个组件需要共享同一份数据。
  • 同一份数据被多处修改,导致不一致。
  • 服务端数据缓存多久?什么时候刷新?
  • 用户断网时如何优雅降级?
  • 多个用户同时修改同一条数据怎么办?

状态管理是前端架构中最容易出问题的领域之一

1.2 状态管理不好的症状

如果你的项目出现以下情况,说明状态管理需要重构:

  • 改了 A 组件的状态,B 组件没更新。
  • 页面刷新后,数据需要重新加载,体验差。
  • 同一个接口被多个组件重复请求。
  • 表单提交后,列表没有自动刷新。
  • 状态逻辑分散在组件里,难以测试和维护。

1.3 生活化比喻

状态管理像城市的水电气系统:

  • 本地状态是你家里的开关,只影响一个房间。
  • 全局状态是小区的总闸,影响多个住户。
  • 服务端状态是城市供水厂,源头在水厂,家里只是缓存。
  • URL 状态是门牌号,告诉外卖员你住哪。
  • 缓存策略是水箱容量和换水频率,太小不够用,太大不新鲜。

好的状态管理,就是让每个状态都在最合适的位置,用最合适的方式流动。


二、状态的分类

2.1 五类状态

状态类型说明示例管理方式
本地 UI 状态只影响单个组件或局部界面弹窗开关、加载状态、表单输入临时值useState / useReducer
全局共享状态多个组件共享用户信息、主题、权限Zustand / Redux / Pinia
服务端状态来自服务端的数据用户列表、订单详情、商品数据React Query / SWR / TanStack Query
URL 状态存储在 URL 中的状态当前页码、筛选条件、选中标签路由参数 / Query String
表单状态表单相关的复杂状态字段值、校验错误、提交状态React Hook Form / Formik

2.2 为什么需要分类?

不同状态有不同的生命周期、更新来源和一致性要求:

  • 本地 UI 状态不需要持久化,不需要共享。
  • 全局共享状态需要跨组件同步,但不需要持久化。
  • 服务端状态的“真相”在服务端,前端只是缓存,需要考虑缓存策略。
  • URL 状态可以分享和回退,适合做页面级状态。
  • 表单状态有复杂的校验和提交逻辑,需要专门管理。

常见反模式:把所有状态都放到 Redux,导致 Store 臃肿、性能差、开发体验差。


三、客户端状态管理

3.1 useState / useReducer

适合本地和局部状态:

tsx
// 简单状态
const [isOpen, setIsOpen] = useState(false);

// 复杂状态用 useReducer
function reducer(state, action) {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'decrement': return { count: state.count - 1 };
    default: return state;
  }
}

3.2 Context API

适合跨组件传递“低频更新”的状态,如主题、语言、用户信息。

tsx
const ThemeContext = createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Child />
    </ThemeContext.Provider>
  );
}

注意:Context 不适合高频更新的状态,因为会导致大量组件重渲染。

3.3 全局状态管理库

特点适用场景
Redux生态成熟、可预测、严格超大型应用、需要强约束
Zustand简单、轻量、灵活中小型应用、快速开发
MobX响应式、面向对象适合 OOP 背景团队
PiniaVue 官方推荐Vue 项目
Jotai / Recoil原子化状态React 项目,细粒度订阅

3.4 选型建议

  • 小项目:useState + Context 足够。
  • 中大型 React 项目:Zustand + React Query 是黄金组合。
  • 需要强审计和调试:Redux Toolkit。
  • Vue 项目:Pinia。

四、服务端状态管理

4.1 服务端状态的特殊性

服务端状态与客户端状态最大的区别:

  • 来源不同:服务端状态的真相在服务端。
  • 需要异步获取:有 loading、error、success 等状态。
  • 需要缓存:避免重复请求。
  • 需要失效策略:数据过期后如何刷新。
  • 需要乐观更新:提升用户体验。
  • 需要去重:多个组件同时请求同一个接口,只发一次请求。

4.2 React Query / SWR / TanStack Query

这些库是服务端状态管理的事实标准。

核心概念:

概念说明
Query读取服务端数据
Mutation修改服务端数据
Cache Key缓存的唯一标识
Stale Time数据被视为“新鲜”的时间
Cache Time数据在缓存中保留的时间
Refetch重新获取数据
Invalidation让缓存失效
Optimistic Update乐观更新

4.3 React Query 示例

tsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// 读取数据
function useUser(userId) {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
    staleTime: 5 * 60 * 1000, // 5 分钟内不重新请求
  });
}

// 修改数据
function useUpdateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data) => fetch('/api/users', { method: 'POST', body: JSON.stringify(data) }),
    onSuccess: () => {
      // 让 user 相关缓存失效
      queryClient.invalidateQueries({ queryKey: ['user'] });
    }
  });
}

4.4 服务端状态设计原则

  1. 每个服务端数据都有唯一的 Query Key
  2. 合理设置 staleTime 和 cacheTime:读多写少的数据可以缓存久一点。
  3. Mutation 后主动失效相关 Query
  4. 乐观更新要提供回滚机制
  5. 错误处理要统一:重试、降级、提示。

五、缓存策略

5.1 缓存的位置

用户 → 浏览器缓存 → CDN 缓存 → Edge 缓存 → BFF 缓存 → 数据库缓存 → 数据库

前端主要关注:

  • HTTP 缓存:Cache-Control、ETag、Last-Modified。
  • 内存缓存:React Query / SWR 的内存缓存。
  • 本地存储:localStorage、IndexedDB、SessionStorage。

5.2 HTTP 缓存策略

策略说明适用场景
Cache-Control: no-cache每次都要向服务端验证频繁变化的数据
Cache-Control: no-store完全不缓存敏感数据
Cache-Control: max-age=3600缓存 1 小时静态资源、变化不频繁的数据
ETag + If-None-Match协商缓存需要验证新鲜度的资源

5.3 前端数据缓存策略

策略说明示例
Stale-While-Revalidate先返回缓存,后台更新React Query 默认策略
Cache First优先缓存,没有再走网络离线应用
Network First优先网络,失败用缓存实时性要求高的数据
Time-Based按时间过期5 分钟刷新一次
Event-Based特定事件触发刷新用户操作后刷新列表

5.4 缓存失效策略

缓存最难的问题是:什么时候让缓存失效

常见策略:

  1. 时间过期:最简单,但可能数据不新鲜。
  2. 主动失效:Mutation 后手动 invalidate。
  3. 事件驱动:WebSocket / SSE 推送数据变化。
  4. 版本号:资源 URL 带版本号,如 app.v2.js
  5. ETag 协商:服务端决定资源是否变化。

六、数据一致性

6.1 一致性模型

模型说明适用场景
强一致性任何时刻所有节点数据一致金融交易、库存扣减
最终一致性允许短暂不一致,最终一致社交网络、内容展示
因果一致性有因果关系的事件顺序一致聊天应用、协作编辑

6.2 前端常见的一致性问题

问题 1:列表和详情数据不一致

用户修改了订单详情,回到列表发现数据还是旧的。

解决

  • Mutation 成功后 invalidate 列表和详情的 Query。
  • 或手动更新缓存中的列表项。

问题 2:多个标签页数据不一致

用户在标签页 A 修改了数据,标签页 B 还是旧数据。

解决

  • 使用 Broadcast Channel 同步状态。
  • 页面重新获得焦点时 refetch。

问题 3:离线后重新上线

用户在离线时做了修改,重新联网后如何同步?

解决

  • 本地队列存储未同步的操作。
  • 联网后按顺序执行,处理冲突。

6.3 乐观更新与回滚

乐观更新假设操作会成功,先更新 UI,再发送请求:

tsx
useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {
    // 取消正在进行的 refetch
    await queryClient.cancelQueries({ queryKey: ['todos'] });
    // 保存之前的值
    const previousTodos = queryClient.getQueryData(['todos']);
    // 乐观更新
    queryClient.setQueryData(['todos'], old => [...old, newTodo]);
    return { previousTodos };
  },
  onError: (err, newTodo, context) => {
    // 出错时回滚
    queryClient.setQueryData(['todos'], context.previousTodos);
  },
  onSettled: () => {
    // 无论成功与否,最终 refetch
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  }
});

6.4 冲突解决策略

策略说明适用场景
Last Write Wins以最后一次写入为准简单场景
Version Vector根据版本号判断冲突协作编辑
Operational Transformation操作转换在线文档
CRDT无冲突复制数据类型离线优先应用

七、URL 状态与表单状态

7.1 URL 状态

URL 是天然的“可分享状态存储”:

/products?category=electronics&page=2&sort=price_asc

优点:

  • 可分享、可回退、可刷新。
  • 不需要额外状态管理。

适合场景:

  • 列表页筛选、排序、分页。
  • 详情页选中项。
  • 搜索条件。

7.2 表单状态

表单是前端最复杂的状态之一,涉及:

  • 字段值
  • 校验状态
  • 错误信息
  • 提交状态
  • 字段联动

推荐使用专门的表单库:

特点
React Hook Form性能优秀、基于非受控组件
FormikAPI 直观、生态丰富
Ant Design Form与 AntD 深度集成

八、最佳实践

  1. 按状态类型选择管理工具:不要把所有状态塞进一个 Store。
  2. 服务端状态用专用库:React Query / SWR / TanStack Query。
  3. 全局状态保持最小:只放真正需要全局共享的状态。
  4. URL 能表达的状态放 URL:筛选、分页、排序。
  5. 合理设置缓存策略:读多写少缓存久,实时性要求高缓存短。
  6. Mutation 后主动失效相关缓存
  7. 乐观更新必须能回滚
  8. 关注离线场景和一致性:尤其是移动端和弱网环境。
  9. 避免 prop drilling:用 Context 或状态管理库解决。
  10. 状态逻辑要可测试:把业务逻辑从组件中抽离。

九、总结

数据与状态管理是前端架构的核心能力:

  • 初级:会用 useState、Context、Redux 管理状态。
  • 中级:能区分五类状态,合理使用 React Query/SWR。
  • 高级:能设计复杂应用的数据流、缓存策略和一致性方案。
  • 架构师:能把状态管理标准化为团队规范,搭建可复用的数据层。

好的状态管理让应用可预测、可维护、可扩展;差的状态管理会让代码迅速失控。


十、延伸阅读与资源

必读文章

书籍

  • 🟡 《Redux 实战》
  • 🔴 《Designing Data-Intensive Applications》— Martin Kleppmann

实践项目

  • 用 React Query 重构一个现有项目的数据层。
  • 实现一个支持离线编辑的 Todo 应用(乐观更新 + 冲突解决)。
  • 设计一个大型列表页的 URL 状态管理方案。

领域编号:A05 数据与状态管理
最后更新:2026-06-18


本领域学习进度

学习进度0 / 43 (0%)

基于 MIT 协议发布