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 与虚拟化的效果,同时用键盘操作列表。先根据数据判断瓶颈,再选择优化。