管理规范化数据( Managing Normalized Data )

管理规范化数据( Managing Normalized Data )

正如标准化状态形状中所述,Normalizr 库经常用于将嵌套响应数据转换为适合集成到商店中的标准化形状。但是,这并没有解决执行对该规范化数据的进一步更新的问题,因为它正在应用程序的其他地方使用。根据自己的喜好,可以使用各种不同的方法。我们将使用向帖子添加新评论的示例。

标准方法

简单合并

一种方法是将动作的内容合并到现有状态中。在这种情况下,我们需要进行深度递归合并,而不仅仅是浅拷贝。Lodash 的 merge函数可以为我们处理这个问题:

import merge from "lodash/merge"; function commentsById(state = {}, action) { switch(action.type) { default : { if(action.entities && action.entities.comments) { return merge{}, state, action.entities.comments.byId } return state; } } }

这需要 reducer 方面的工作量最少,但确实需要动作创建者可能会做相当多的工作,以在动作分派之前将数据组织为正确的形状。它也不处理试图删除一个项目。

切片Reducer组成

如果我们有切片缩减器的嵌套树,则每个切片 reducer 都需要知道如何适当地响应此操作。我们需要在行动中包含所有相关数据。我们需要用评论 ID 更新正确的 Post 对象,使用该 ID 作为关键字创建一个新的评论对象,并将评论 ID 包含在所有评论 ID 列表中。以下是关于这件作品可能如何组合在一起的方法:

// actions.js function addComment(postId, commentText) { // Generate a unique ID for this comment const commentId = generateId("comment" return { type : "ADD_COMMENT", payload : { postId, commentId, commentText } }; } // reducers/posts.js function addComment(state, action) { const {payload} = action; const {postId, commentId} = payload; // Look up the correct post, to simplify the rest of the code const post = state[postId]; return { ...state, // Update our Post object with a new "comments" array [postId] : { ...post, comments : post.comments.concat(commentId) } }; } function postsById(state = {}, action) { switch(action.type) { case "ADD_COMMENT" : return addComment(state, action default : return state; } } function allPosts(state = [], action) { // omitted - no work to be done for this example } const postsReducer = combineReducers{ byId : postsById, allIds : allPosts } // reducers/comments.js function addCommentEntry(state, action) { const {payload} = action; const {commentId, commentText} = payload; // Create our new Comment object const comment = {id : commentId, text : commentText}; // Insert the new Comment object into the updated lookup table return { ...state, [commentId] : comment }; } function commentsById(state = {}, action) { switch(action.type) { case "ADD_COMMENT" : return addCommentEntry(state, action default : return state; } } function addCommentId(state, action) { const {payload} = action; const {commentId} = payload; // Just append the new Comment's ID to the list of all IDs return state.concat(commentId } function allComments(state = [], action) { switch(action.type) { case "ADD_COMMENT" : return addCommentId(state, action default : return state; } } const commentsReducer = combineReducers{ byId : commentsById, allIds : allComments }

这个例子有点长,因为它展示了所有不同的切片减速器和 reducer 如何配合在一起。请注意这里涉及的代表团。postsById切片 reducer 为代表这种情况下工作addComment,这将插入新的评论的 ID 为正确的 Post 物品。同时,commentsByIdreducer 和allCommentsreducer 都有自己的大小写 reducer,它们会更新评论查找表和所有评论ID的列表。

其他方法

基于任务的更新

由于 reducer 只是函数,所以有无数的方法来分解这个逻辑。虽然使用切片 reducer 显然是最常见的,但也可以在更多以任务为导向的结构中组织行为。因为这通常会涉及更多的嵌套更新,所以您可能需要使用不可变更新实用程序库(如dot-prop-immutableobject-path-immutable)来简化更新语句。以下是可能的样例:

import posts from "./postsReducer"; import comments from "./commentsReducer"; import dotProp from "dot-prop-immutable"; import {combineReducers} from "redux"; import reduceReducers from "reduce-reducers"; const combinedReducer = combineReducers{ posts, comments } function addComment(state, action) { const {payload} = action; const {postId, commentId, commentText} = payload; // State here is the entire combined state const updatedWithPostState = dotProp.set( state, `posts.byId.${postId}.comments`, comments => comments.concat(commentId) const updatedWithCommentsTable = dotProp.set( updatedWithPostState, `comments.byId.${commentId}`, {id : commentId, text : commentText} const updatedWithCommentsList = dotProp.set( updatedWithCommentsTable, `comments.allIds`, allIds => allIds.concat(commentId return updatedWithCommentsList; } const featureReducers = createReducer{}, { ADD_COMMENT : addComment, }; const rootReducer = reduceReducers( combinedReducer, featureReducers

这种方法非常清楚这种情况发生了什么"ADD_COMMENTS",但它确实需要嵌套的更新逻辑以及状态树形状的一些特定知识。根据你想要编写你的 reducer 逻辑的方式,这可能会也可能不需要。

终极版-ORM

终极版-ORM 库提供用于在终极版商店管理标准化数据一个非常有用的抽象层。它允许你声明 Model 类并定义它们之间的关系。然后,它可以为您的数据类型生成空的“表”,作为查找数据的专用选择器工具,并对该数据执行不可变的更新。

有几种方法可以使用 Redux-ORM 执行更新。首先,Redux-ORM 文档建议在每个 Model 子类上定义 Reducer 函数,然后在自己的商店中包含自动生成的组合 reducer 函数:

// models.js import {Model, many, Schema} from "redux-orm"; export class Post extends Model { static get fields() { return { // Define a many-sided relation - one Post can have many Comments, // at a field named "comments" comments : many("Comment") }; } static reducer(state, action, Post) { switch(action.type) { case "CREATE_POST" : { // Queue up the creation of a Post instance Post.create(action.payload break; } case "ADD_COMMENT" : { const {payload} = action; const {postId, commentId} = payload; // Queue up the addition of a relation between this Comment ID // and this Post instance Post.withId(postId).comments.add(commentId break; } } // Redux-ORM will automatically apply queued updates after this returns } } Post.modelName = "Post"; export class Comment extends Model { static get fields() { return {}; } static reducer(state, action, Comment) { switch(action.type) { case "ADD_COMMENT" : { const {payload} = action; const {commentId, commentText} = payload; // Queue up the creation of a Comment instance Comment.create{id : commentId, text : commentText} break; } } // Redux-ORM will automatically apply queued updates after this returns } } Comment.modelName = "Comment"; // Create a Schema instance, and hook up the Post and Comment models export const schema = new Schema( schema.register(Post, Comment // main.js import { createStore, combineReducers } from 'redux' import {schema} from "./models"; const rootReducer = combineReducers{ // Insert the auto-generated Redux-ORM reducer. This will // initialize our model "tables", and hook up the reducer // logic we defined on each Model subclass entities : schema.reducer() } // Dispatch an action to create a Post instance store.dispatch{ type : "CREATE_POST", payload : { id : 1, name : "Test Post Please Ignore" } } // Dispatch an action to create a Comment instance as a child of that Post store.dispatch{ type : "ADD_COMMENT", payload : { postId : 1, commentId : 123, commentText : "This is a comment" } }

Redux-ORM 库维护要应用的内部更新队列。这些更新然后不断地应用,简化了更新过程。

另一个变体是在单个案例缩减器中使用 Redux-ORM 作为抽象层:

import {schema} from "./models"; // Assume this case reducer is being used in our "entities" slice reducer, // and we do not have reducers defined on our Redux-ORM Model subclasses function addComment(entitiesState, action) { const session = schema.from(entitiesState const {Post, Comment} = session; const {payload} = action; const {postId, commentId, commentText} = payload; const post = Post.withId(postId post.comments.add(commentId Comment.create{id : commentId, text : commentText} return session.reduce( }

总的来说,Redux-ORM 提供了一组非常有用的抽象概念,用于定义数据类型之间的关系,在我们的状态下创建“表”,检索和反规范化关系数据以及将不可变更新应用于关系数据。