Redux Toolkit/RTKQ 如何暂存复杂列表的待提交编辑数据?
我是Redux与RTKQ的新手,怀疑自己遗漏了核心使用逻辑。下述是非常通用的业务场景,我不相信框架没有对应的内置解决方案。
高层业务流程
- 从后端拉取数据
- 用户对拉取到的部分数据进行编辑修改
- 用户在特定操作节点点击按钮,将修改持久化提交到后端
场景细节
拉取到的数据是profile列表,每个profile包含name字段与contacts联系人列表,数据结构示例如下:
[ { uuid: '<some id>', name: '<SomeName>', contacts: [ { uuid: '<some id>', label: 'Email', type: 'email', value: '<some email>', }, ], }, { uuid: '<some other id>', name: '<SomeName>', contacts: [ { uuid: '<some other id>', label: 'Email', type: 'email', value: '<some email>', }, ], }, ];
用户支持的操作包括:新增profile、修改已有profile或联系人信息、为指定profile新增联系人。
和常规React项目实现一致,该嵌套数据结构通过多层组件拆分渲染,当前组件层级结构如下:
EditProfiles (handles saving/deletion of profiles) -> ProfileList -> ProfileListItem (transiently stores changes to the name) -> EditContacts (transiently stores updates to channels or the creation of new channels) -> EditContactList -> EditContactListItem
其中EditProfiles组件负责从后端拉取数据,在用户点击保存/删除按钮时将更新提交到服务端。
核心疑问
提交更新前,应当如何正确暂存用户的修改内容?
数据拉取完成后会由RTKQ自动存储缓存,这一步没有问题,但当用户需要修改已拉取的数据时问题就出现了:提交前的修改内容应该存储在哪里?
我无法将修改存为React状态,因为面对复杂嵌套对象时,这种实现方式很容易出现各类隐患;如果直接修改RTKQ缓存中的数据,一旦对应查询因任何原因重新发起,所有未提交的修改都会被覆盖。
我最终采用的方案是新建独立的slice存储所有修改后的数据,这种并行存储结构可以保证即使查询重新发起,用户的修改也不会丢失,且无论对象复杂度如何,只要正确监听状态,变更都会触发对应组件重渲染。
但该方案存在三个明显缺陷:
- 需要手动维护这个slice:用户修改内容时要同步更新状态,提交成功后还要手动清理对应变更,丢失了RTKQ自带的很多自动化能力;
- 存在数据冗余:多数场景下用户仅修改对象的部分字段,却需要存储完整的对象数据;
- 手动维护逻辑带来了很多仅使用RTKQ时不会存在的潜在错误点。
最终问题
是否存在我尚未了解的、更符合RTKQ设计范式的标准解决方案?
RTKQ的核心定位是服务端状态同步工具,从设计之初就不负责托管客户端本地的临时编辑态——你之前遇到的所有问题,本质都是混淆了两类状态的职责边界:直接修改RTKQ缓存会被自动重拉取覆盖,单独建全量副本slice又会带来冗余和维护成本。
符合RTK/RTKQ设计范式的标准方案是做增量草稿态分离存储,不需要存全量数据,只存和服务端原始值有差异的部分,配合RTK内置能力可以把维护成本压到最低,完全规避你提到的三个缺陷:
具体实现逻辑
- 单独建一个
profileDrafts的轻量slice,完全不需要存储全量profile数据,只存三类增量信息:- 还没提交到后端的新建profile列表
- 已有profile的修改项:用profile的uuid做key,value只存被修改过的字段,比如
{ [profileUuid]: { name: '新名字' } } - 每个profile下的新建联系人、已有联系人的修改项,同样用对应uuid做key,只存变更字段
- 封装两个通用selector,把数据合并逻辑完全从组件层抽离:
- 第一个selector读取RTKQ缓存里的原始profile数据,自动叠加drafts里的增量修改、新建项,输出组件渲染直接可用的带编辑态的完整列表
- 第二个selector读取drafts里的变更记录,自动生成提交给后端的patch参数,不需要在提交节点手动拼接请求数据
- 给对应profile的查询端点配置
merge回调,当后台触发自动重拉取时,不会直接用新返回的数据覆盖缓存,而是先和本地drafts做合并,只要用户没提交,修改就不会因为接口重发丢失 - 提交逻辑直接走RTKQ的mutation,在mutation的
onQueryStarted生命周期或者标签失效触发缓存更新后,自动把已经成功提交的项从drafts slice里清除,不需要手动写清理逻辑
方案合理性说明
- 没有冗余存储:原始服务端数据始终由RTKQ缓存托管,完整享受RTKQ自带的缓存失效、自动重拉、内存回收能力,草稿态只存差异部分
- 不需要在嵌套组件里分散存状态:不管组件层级多深,直接调用selector拿合并后的数据、调用action更新草稿即可,不会出现多层组件状态不同步的问题
- 职责边界完全符合Redux设计原则:服务端真实状态归RTKQ管理,客户端临时编辑状态归普通slice管理,不会出现状态互相覆盖的异常
注意:不要采用“直接修改RTKQ缓存存草稿”的方案,这个做法完全违背RTKQ的设计逻辑——RTKQ的缓存随时可能因为标签失效、组件卸载、内存回收策略被重置,未提交的修改丢失只是时间问题。
内容的提问来源于stack exchange,提问作者zetain

