一、状态复用概述
什么是状态复用
状态复用,说白了就是「把已经算好的结果存起来,下次直接用」。
举个例子。你写了一个搜索框,用户每敲一个字就发请求。如果每次都重新查数据库,那服务器早崩了。我习惯的做法是:把最近 10 次搜索结果缓存到内存里,用户再搜同样的关键词,直接返回缓存数据。这就是状态复用最朴素的形式。
在实时更新场景下,状态复用要解决的问题更复杂一些。它不只是缓存,还包括:
- 跨组件共享:多个组件需要同一份数据,不用各自拉取
- 跨页面保持:用户从列表页跳到详情页再回来,列表状态还在
- 跨会话延续:用户刷新页面甚至关掉浏览器再打开,某些状态还能恢复
核心定义:状态复用是指在应用的不同生命周期阶段(组件挂载/卸载、页面切换、会话重启),通过合理的存储和恢复策略,避免重复计算或重复请求,从而提升性能和用户体验。
为什么需要状态复用
我刚开始做前端那会儿,觉得状态管理就是 Redux 那一套。后来在实时数据项目里踩了坑,才明白事情没那么简单。
给你列几个真实场景:
| 场景 | 不复用的后果 | 复用后的收益 |
|---|---|---|
| 用户频繁切换 Tab | 每次切回来都重新请求,白屏等待 | 瞬间展示,零等待 |
| 列表页 → 详情页 → 返回列表 | 滚动位置丢失,数据重新加载 | 滚动位置保留,数据秒开 |
| WebSocket 推送频繁 | 每次推送都全量更新 DOM | 只更新变化的部分 |
| 用户填写长表单后误刷新 | 所有填写内容丢失 | 自动恢复,用户不感知 |
你看,状态复用不只是「优化」,它直接决定了用户觉得你的应用「卡不卡」、「好不好用」。我记得有一次做股票行情看板,每秒推送几十条数据。如果不做状态复用,页面直接卡死。后来用了增量更新 + 缓存池,CPU 占用从 80% 降到了 15%。
实时更新场景的挑战
实时更新场景下做状态复用,跟普通场景完全不是一个量级。我总结了几大难点:
挑战一:数据一致性
实时数据一直在变。你缓存了一份数据,下一秒服务器告诉你「那条数据已经变了」。这时候你是更新缓存,还是丢弃缓存重新拉?
我曾经在一个在线协作编辑项目里遇到这个问题。两个人同时编辑同一个文档,A 的本地缓存是「版本 10」,B 提交了「版本 11」。如果 A 的缓存不更新,他看到的永远是旧数据。但如果每次推送都全量更新缓存,性能又扛不住。
我的经验:对于实时数据,不要全量缓存。只缓存「不变的部分」,比如用户信息、配置项。实时变化的数据用「订阅 + 增量更新」策略,每次只推送变化字段。
挑战二:内存管理
实时应用跑得越久,积累的状态越多。如果不做清理,内存迟早爆掉。
想想看:一个聊天应用,用户打开了 50 个群聊。每个群聊的历史消息都缓存在内存里。手机内存才多大?
- LRU 淘汰:最近最少使用的缓存优先清除
- TTL 过期:超过一定时间的数据自动失效
- 按场景分级:当前可见的页面用强引用,不可见的用弱引用
注意:不要为了复用而无限缓存。我曾经见过一个项目,缓存了所有历史数据,结果页面打开 30 分钟后内存占用超过 500MB。用户手机直接闪退。后来加了 LRU 限制,内存稳定在 80MB 以内。
挑战三:状态同步时机
实时数据什么时候该同步?什么时候该用缓存?这个时机很难把握。
举个例子:
- 用户正在看股票 K 线图,每秒都有新数据进来。你是每 1 秒刷新一次界面,还是攒够 10 条再批量更新?
- 用户切换到后台看别的页面,这时候数据还要不要继续拉?
- 用户切回来,是直接用缓存,还是先发请求确认数据是最新的?
这些问题没有标准答案。我个人的做法是:根据数据的重要性和实时性要求,给每条数据打标签。高实时性的数据(如股票价格)走 WebSocket 推送,低实时性的数据(如用户资料)走缓存 + 定期刷新。
挑战四:跨组件/跨页面状态共享
实时应用里,同一个数据可能被多个组件、多个页面同时使用。比如用户信息,导航栏要用、个人中心要用、设置页面也要用。
如果每个组件都自己拉一份,不仅浪费请求,还会出现数据不一致——导航栏显示「张三」,个人中心显示「张三(已改名)」。这就尴尬了。
我建议用「单一数据源」模式:
// 单一数据源示例
const userStore = {
data: null,
subscribers: new Set(),
// 订阅数据变化
subscribe(callback) {
this.subscribers.add(callback);
return () => this.subscribers.delete(callback);
},
// 更新数据并通知所有订阅者
setData(newData) {
this.data = newData;
this.subscribers.forEach(cb => cb(newData));
}
};
这样所有组件都从同一个地方拿数据,数据变了大家一起更新。不会出现「一个改了另一个不知道」的情况。
本章知识体系
说了这么多,我把本章的核心逻辑画成了一张图,方便你理解:
这张图把本章的核心脉络串起来了。你从上往下看:先搞清楚「为什么需要状态复用」,再明确「复用什么东西」,最后思考「怎么复用」。而实时更新场景给这三个环节都带来了额外的挑战。
嗯,这一章算是开个头。后面的章节会逐个深入这些挑战,给出具体的解决方案和代码实现。你先把基础概念理清楚,后面学起来会轻松很多。