Redux带嵌套回复的Feed评论数据规范化方案及normalizr使用咨询
嵌套评论的Redux规范化存储方案
按照Redux官方的规范化核心思路,所有评论(无论是挂载在Feed下的一级评论,还是挂载在其他评论下的多级楼中楼回复)本质是同一种实体,不需要为回复单独开辟独立存储位置,只需要统一做扁平化拆解即可。
具体的存储结构设计
核心拆分规则:
- Feed实体仅存储自身属性 + 关联的一级评论ID列表,不保存嵌套的评论对象
- 所有评论(一级评论、各级深度回复)统一存入
comments.byId字典,不同层级的评论实体结构完全一致,通过3个关联字段标记从属关系:feedId: 冗余标记评论所属的Feed ID,避免后续查询时递归追溯所属FeedreplyToCommentId: 标记当前评论挂载的上级评论ID,一级评论的该字段值为nullreplyCommentIds: 存储当前评论下所有直接下级回复的ID列表,不存储嵌套的回复对象
comments.allIds数组保存所有评论的全局ID,方便全局统计、批量操作
最终的完整状态结构示例:
{ feeds: { byId: { "feed_id_1": { id: "feed_id_1", content: "Feed内容", // 仅存当前Feed下的一级评论ID列表 commentIds: ["comment_id_1"] } }, allIds: ["feed_id_1"] }, comments: { byId: { // 一级评论,和回复使用完全一致的实体结构 "comment_id_1": { id: "comment_id_1", content: "一级评论内容", feedId: "feed_id_1", replyToCommentId: null, // 仅存当前评论下直接关联的回复ID列表 replyCommentIds: ["comment_reply_id_1", "comment_reply_id_2"] }, // 一级评论下的回复 "comment_reply_id_1": { id: "comment_reply_id_1", content: "第一条回复内容", feedId: "feed_id_1", replyToCommentId: "comment_id_1", replyCommentIds: [] }, "comment_reply_id_2": { id: "comment_reply_id_2", content: "第二条回复内容", feedId: "feed_id_1", replyToCommentId: "comment_id_1", replyCommentIds: [] } }, // 所有评论(一级+各级回复)的全局ID列表 allIds: ["comment_id_1", "comment_reply_id_1", "comment_reply_id_2"] } }
这种设计的实际优势
- 不受评论层级深度限制:哪怕产品后续支持无限层楼中楼回复,实体结构也不需要调整,不会出现深层嵌套导致的不可变更新复杂度飙升的问题
- 状态修改效率高:修改任意评论内容、新增/删除回复时,不需要递归遍历嵌套结构,直接通过ID定位到对应实体修改即可
- 完全解决嵌套回复的存储疑问:评论回复本质就是Comment实体,和一级评论没有结构差异,不需要单独考虑存储位置
是否需要引入normalizr库
直接给出明确的判断标准:
- 满足以下任意一种情况时,完全不需要引入normalizr:
- 项目内需要规范化的实体类型少于3种(比如仅包含Feed、Comment两类)
- API返回的评论嵌套层级固定,没有复杂的多实体交叉关联
- 不想为简单的扁平化逻辑引入额外第三方依赖
这种场景下手写几十行的递归遍历函数就能完成所有嵌套数据的扁平化转换,逻辑可控、调试简单,没有额外学习成本。
- 只有当满足以下条件时,引入normalizr才具备投入产出比:
- 项目内有10种以上存在嵌套关联的实体类型(比如用户、标签、媒体资源、通知、评论等多类交叉关联)
- 团队已经熟悉normalizr的schema配置规则,能大幅减少重复编写的递归转换代码量
手写实现数据规范化的参考代码:
// 递归遍历实现嵌套Feed、评论的扁平化转换,无第三方依赖 function normalizeFeedList(rawFeedList) { const normalizedState = { feeds: { byId: {}, allIds: [] }, comments: { byId: {}, allIds: [] } } rawFeedList.forEach(rawFeed => { // 初始化Feed实体 normalizedState.feeds.byId[rawFeed.id] = { id: rawFeed.id, content: rawFeed.content, commentIds: [] } normalizedState.feeds.allIds.push(rawFeed.id) // 递归处理嵌套评论 const traverseComments = (comments, parentCommentId = null) => { comments.forEach(comment => { // 初始化评论实体 normalizedState.comments.byId[comment.id] = { id: comment.id, content: comment.content, feedId: rawFeed.id, replyToCommentId: parentCommentId, replyCommentIds: [] } normalizedState.comments.allIds.push(comment.id) // 建立实体间的关联关系 if (parentCommentId) { normalizedState.comments.byId[parentCommentId].replyCommentIds.push(comment.id) } else { normalizedState.feeds.byId[rawFeed.id].commentIds.push(comment.id) } // 递归处理嵌套的回复内容 if (comment.comments?.length) { traverseComments(comment.comments, comment.id) } }) } traverseComments(rawFeed.comments || []) }) return normalizedState }
内容的提问来源于stack exchange,提问作者Phát Nguyễn
相关产品推荐
相关产品推荐

