Redux嵌套一对多关系场景下,是否需要使用完全扁平化的归一化state?
你没有理解错,Redux的扁平化归一化是针对通用关联场景的最佳实践,如果你的业务是严格的封闭树状一对多结构,嵌套的{allIds, byId}结构完全可用,甚至开发效率更高。你漏掉的是扁平化在以下几个非树状场景下的核心收益:
- 跨层级直接访问需求
比如全局搜索句子、批量修改所有句子内容,扁平化只需要遍历顶层的sentences集合即可,嵌套结构需要递归遍历所有书籍→所有页面→所有句子,代码复杂度和性能都会随数据量上升快速劣化。如果你的需求里有根据单个sentenceId直接获取数据的场景(比如分享单个句子、收藏句子),扁平化结构只需要传1个id即可定位数据,嵌套结构需要同时传递bookId、pageId、sentenceId三个参数,参数传递链路长了之后很容易出现遗漏、传错的问题。 - 业务扩展后的关联复用
你现在的业务设定是句子唯一属于页面、页面唯一属于书籍,但如果后续业务调整,比如支持同一段句子复用在多本书的多个页面、支持单独的页面模板库,扁平化结构只需要给对应的关联数组加id即可,数据只存一份,修改一次全量生效。嵌套结构下要实现相同逻辑只能复制数据,很容易出现多副本更新不一致的问题。 - 对接通用工具链的成本更低
目前Redux生态里的很多工具(比如RTK Query、normalizr)默认都是适配扁平化归一化结构的,后端接口返回的关联数据通常也都是扁平化结构,直接对接扁平化的state不需要额外做嵌套组装的逻辑,长期维护成本更低。
你提到的克隆书籍的问题有标准的解决路径:不需要在action creator或者reducer里生成id,你可以在thunk/RTK Query的异步逻辑里先读取原书的完整关联数据,遍历生成所有新的全局唯一id,组装好完整的新书籍、页面、句子数据之后再dispatch action交给reducer写入即可,完全符合Redux的纯函数规范。
最后要说明:没有绝对正确的状态结构,如果你能确定当前和未来的业务都不会出现跨层级访问、数据复用的需求,完全可以继续用你现在的嵌套结构,不需要为了遵循规范而过度设计。如果业务有扩展的可能,提前做扁平化可以避免后续重构的成本。
内容的提问来源于stack exchange,提问作者Guillaume Brunerie
相关产品推荐
相关产品推荐

