← Frontend / React

03_复杂状态与性能

React useReducer、Context、请求副作用与性能量测。

React 复杂状态与性能

学习目标:在页面规模增长后合理使用 useReducer、Context 和性能工具,避免把所有问题都交给全局状态或 memo。

1. 先按状态来源分类

弹窗开关和未提交输入属于本地 UI 状态;搜索词、页码适合放 URL;笔记列表来自服务端,需要缓存、失效和重新获取。三者生命周期不同,混在一个全局对象里会造成同步与过期问题。

useReducer 适合一个组件中存在多个相关状态转移,例如表单的编辑、提交、成功、失败;它不能自动解决跨页面共享或网络缓存问题。

type State =
  | { status: 'editing'; title: string }
  | { status: 'saving'; title: string }
  | { status: 'error'; title: string; message: string };

type Action =
  | { type: 'change'; title: string }
  | { type: 'submit' }
  | { type: 'fail'; message: string };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'change': return { status: 'editing', title: action.title };
    case 'submit': return { status: 'saving', title: state.title };
    case 'fail': return { status: 'error', title: state.title, message: action.message };
  }
}

把互斥状态写成判别联合,比多个布尔值同时为真更容易检查。实际提交成功后可由路由导航或增加 success 分支处理;reducer 应是纯函数,不在其中发请求。

2. Context 是传递机制

Context 能把主题、语言、当前用户等值传给子树。Provider 值变化会影响消费它的组件;把快速变化的大对象塞入单个 Context,可能让无关消费者频繁重新渲染。按职责拆分 Provider,或把状态保留在真正需要它的最近共同父组件。Context 不替代服务端缓存,也不替代 URL。

3. 正确测量再优化

重新渲染不一定慢。先在生产构建或接近生产的数据量下,用 React Profiler 和浏览器性能工具定位耗时组件;再考虑 memo、useMemo、useCallback、列表虚拟化或拆分状态。过度 memo 会增加代码和比较成本,依赖数组错误还可能让组件看到旧值。

const visibleNotes = notes.filter(note =>
  note.title.toLocaleLowerCase().includes(query.toLocaleLowerCase())
);

过滤小列表可直接在渲染时计算,不必先用 Effect 存另一份状态。只有当计算成本被量测证明显著时,再用 useMemo。大列表虚拟化要检查键盘导航、焦点保持和辅助技术语义。

4. 副作用与并发请求

请求依赖查询参数时,在参数变化时取消旧请求或交给数据获取层管理;写入完成后失效相关缓存。开发模式的 Strict Mode 可能额外执行 Effect 的设置与清理,借此检验清理逻辑是否正确。不要靠关闭 Strict Mode 掩盖订阅泄漏或重复请求问题。

练习:实现搜索列表并记录 Profiler;从 20 项逐步增加到 2000 项,比较直接过滤、memo 与虚拟化的效果,同时用键盘操作列表。先根据数据判断瓶颈,再选择优化。