非常教程

Redux参考手册

方法 | Recipes

管理规范化数据( 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-immutable或object-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 提供了一组非常有用的抽象概念,用于定义数据类型之间的关系,在我们的状态下创建“表”,检索和反规范化关系数据以及将不可变更新应用于关系数据。

Redux

Redux由Dan Abramov在2015年创建的科技术语。是受2014年Facebook的Flux架构以及函数式编程语言Elm启发。很快,Redux因其简单易学体积小在短时间内成为最热门的前端架构。

主页 http://redux.js.org/
源码 https://github.com/reactjs/redux/
发布版本 3.7.2

Redux目录

1.高级 | Advanced
2.API
3.基础 | Basics
4.FAQ
5.介绍 | Introduction
6.其他 | Miscellaneous
7.方法 | Recipes