限制Ngrx entity存储大小 是否应在reducer中完成旧数据清理
结论
你完全应该将旧数据清理逻辑放到reducer的upsert操作流程中完成,这是比你当前实现更合理、更符合Ngrx设计规范的方案。
当前实现的缺陷
- 存在不必要的状态变更循环:删除操作会更新Entity状态,再次触发
selectTotal$的监听回调,哪怕删除后已满足容量要求,也会产生多余的流计算开销,若有其他业务逻辑监听总数量或id列表,还可能引发意外的副作用。 - 状态变更不具备原子性:upsert操作和删除操作是分开的两次状态变更,在高频websocket数据推送的场景下,可能出现连续插入多条数据后才触发删除的情况,导致存储短时间内超出阈值,极端情况还会出现竞态问题。
- 需要额外维护订阅生命周期:你需要手动管理这个subscribe的销毁逻辑,遗漏的话会产生内存泄漏。
放到reducer中处理的优势
- 状态变更原子化:upsert和清理逻辑在同一次reducer计算中完成,最终返回的状态永远不会超出你设置的
MAX_COUNT上限,没有中间态的超阈值风险。 - 没有额外的订阅开销:不需要再监听总数量,自然不会出现循环触发的问题,性能更稳定。
- 相关逻辑收敛:所有和该Entity存储相关的逻辑都聚合在reducer中,后续维护不需要在组件、effect中四处查找容量控制的代码,可维护性更好。
参考实现代码
import { createReducer, on } from '@ngrx/store'; import { yourEntityAdapter, YourEntityState } from './your-entity.state'; import { upsertYourEntity } from './your-entity.actions'; // 定义存储容量上限 const MAX_COUNT = 100; const initialState: YourEntityState = yourEntityAdapter.getInitialState(); export const yourEntityReducer = createReducer( initialState, on(upsertYourEntity, (state, { entity }) => { // 先执行新增/更新操作 const stateAfterUpsert = yourEntityAdapter.upsertOne(entity, state); const currentTotal = stateAfterUpsert.ids.length; // 未超出容量直接返回 if (currentTotal <= MAX_COUNT) { return stateAfterUpsert; } // 计算需要删除的最早条目数 const removeCount = currentTotal - MAX_COUNT; // Ngrx Entity的ids数组默认按插入顺序排列,数组头部为最早存入的id const idsToRemove = stateAfterUpsert.ids.slice(0, removeCount) as (string | number)[]; // 执行删除后返回最终状态 return yourEntityAdapter.removeMany(idsToRemove, stateAfterUpsert); }) );
注意事项
- 如果你给Entity Adapter配置了自定义的
sortComparer,导致ids数组不是按插入顺序排列,你可以给每条数据增加insertTimestamp字段,清理前先按时间戳排序再筛选需要删除的id即可。 - 如果你有批量插入的场景,只需要在
upsertMany对应的reducer处理逻辑中套用相同的清理规则即可。 - 实现完成后记得删除你原来的
selectTotal$订阅逻辑,避免重复执行删除操作。
内容的提问来源于stack exchange,提问作者Chi
相关产品推荐
相关产品推荐

