React Redux-带CRUD的Reducer-最佳做法?

时间:2018-07-19 12:16:21

标签: javascript reactjs redux reducers

我为User实体编写了简单的reducer,现在我想在切换操作类型和返回状态时为其应用最佳实践。只需提一下,我在单独的文件actionsTypes.js中提取了动作类型。

actionsTypes.js的内容:

export const GET_USERS_SUCCESS = 'GET_USERS_SUCCESS';
export const GET_USER_SUCCESS = 'GET_USER_SUCCESS';
export const ADD_USER_SUCCESS = 'ADD_USER_SUCCESS';
export const EDIT_USER_SUCCESS = 'EDIT_USER_SUCCESS';
export const DELETE_USER_SUCCESS = 'DELETE_USER_SUCCESS';

第一个问题,是否有针对FAILED案例的操作类型?例如,要添加GET_USERS_FAILED等等并在usersReducer中处理它们?

减根剂是:

const rootReducer = combineReducers({
    users
});

有usersReducer的代码,我在代码中放入了注释/问题,并寻求答案(什么是处理操作类型的最佳做法):

export default function usersReducer(state = initialState.users, action) {
    switch (action.type) {
        case actionsTypes.GET_USERS_SUCCESS:
        // state of usersReducer is 'users' array, so I just return action.payload where it is array of users. Will it automatically update users array on initial state?
            return action.payload;
        case actionsTypes.GET_USER_SUCCESS:
        // What to return here? Just action.payload where it is just single user object? 
            return ;
        case actionsTypes.ADD_USER_SUCCESS:
        // what does this mean? Can someone explain this code? It returns new array, but what about spread operator, and object.assign?
            return [...state.filter(user => user.id !== action.payload.id),
                Object.assign({}, action.payload)];
        case actionsTypes.EDIT_USER_SUCCESS:
        // is this ok?
            const indexOfUser = state.findIndex(user => user.id === action.payload.id);
            let newState = [...state];
            newState[indexOfUser] = action.payload;
            return newState;
        case actionsTypes.DELETE_USER_SUCCESS:
        // I'm not sure about this delete part, is this ok or there is best practice to return state without deleted user?
            return [...state.filter(user => user.id !== action.user.id)];
        default:
            return state;
    }
}

1 个答案:

答案 0 :(得分:2)

我不是一位经验丰富的开发人员,但请允许我回答您的问题,直到现在为止我所学到的知识。

  

第一个问题,是否有针对FAILED案例的操作类型是强制性的?   例如,添加GET_USERS_FAILED等,并在内部进行处理   usersReducer?

这不是强制性的,但是如果您打算向客户提供反馈,那将是很好的。例如,您启动了GET_USERS进程,但由于某种原因它失败了。客户端没有任何反应,没有更新等。因此,您的客户端不知道它失败了,并且想知道为什么什么也没发生。但是,如果您遇到故障案例并发现错误,则可以通知您的客户有错误。

要执行此操作,您可以在两个示例中使用GET_USERS_FAILED操作类型。 userReducers中的一个,而errorfeedback的减速器则为一个。第一个返回状态,因为您的过程失败了,您无法获得所需的数据,因此无论如何都不想改变状态。第二个更新您的反馈减少器并可以更改状态,假设为error,并且您在组件中捕获了此状态,并且如果error状态为true,则会向客户端显示一条好消息。

  

usersReducer的状态是'users'数组,所以我只返回   action.payload,它是用户数组。会自动吗   在初始状态下更新用户数组?

case actionsTypes.GET_USERS_SUCCESS:
    return action.payload;

如果您通过单个请求获取整个用户,则可以。这意味着作为数组的action.payload成为您的状态。但是,如果您不希望通过单个请求获取所有用户(例如分页),那还不够。您需要与获取的状态保持一致。

case actionsTypes.GET_USERS_SUCCESS:
    return [...state, ...action.payload];

在这里,我们正在使用spread syntax

很显然,它可以扩展所提供的内容:)您可以以多种方式将其用于数组以及对象。您可以检查文档。但是这里有一些简单的例子。

const arr = [ 1, 2, 3 ];
const newArr = [ ...arr, 4 ];
// newArr is now [ 1, 2, 3, 4 ]

我们将arr散布到一个新数组中,并向其中添加4。

const obj = { id: 1, name: "foo, age: 25 };
const newObj = { ...obj, age: 30 };
// newObj is now { id: 1, name: "foo", age: 30 }

在这里,我们将obj散布到一个新对象中,并更改了它的age属性。在两个示例中,我们都不会对原始数据进行变异。

  

这里返回什么?只是action.payload,其中只有单个用户   对象?

case actionsTypes.GET_USER_SUCCESS:
    return ;

可能您不能直接在此reducer中使用此操作。因为这里的状态将您的用户保留为数组。您想对拥有某种方式的用户做什么?假设您要保留“选定”用户。您可以为此创建一个单独的reducer或在此处更改您的状态,使其成为对象并持有selectedUser属性并对此进行更新。但是,如果您更改状态的形状,则所有其他异径管零件都需要更改,因为您的状态将是这样的:

{
    users: [],
    selectedUser,
}

现在,您的状态不再是数组,而是一个对象。您必须按照此更改所有代码。

  

这是什么意思?有人可以解释此代码吗?它返回新的   数组,但是散布运算符和object.assign呢?

case actionsTypes.ADD_USER_SUCCESS:
    return [...state.filter(user => user.id !== action.payload.id), Object.assign({}, action.payload)];

我已经尝试解释传播语法。 Object.assign将一些值复制到目标或对其进行更新或合并其中两个。该代码有什么作用?

首先,它获取您的状态,对其进行过滤,并返回不等于您action的用户。有效负载是要添加的用户。这将返回一个数组,因此将其展开并将其与Object.assign部分合并。在Object.assign部分中,它需要一个空对象并将其与用户合并。所有这些值将创建一个新数组,这是您的新状态。假设您的状态是:

[
    { id: 1, name: "foo" },
    { id: 2, name: "bar" },
] 

您的新用户是:

{
     id: 3, name: "baz"
}

这是此代码的作用。首先,它过滤所有用户,并且由于过滤条件不匹配,它将返回所有用户(状态),然后将其散布(别忘了,过滤器返回一个数组,然后我们将该数组散布到另一个数组中):

[ { id: 1, name: "foo"}, { id: 2, name: "bar" } ]

现在,Object.assign部分执行其工作,并将空对象与用户对象action.payload合并。现在我们的最终数组将是这样的:

[ { id: 1, name: "foo"}, { id: 2, name: "bar" }, { id: 3, name: "baz" } ]

但是,实际上,这里不需要Object.assign。为什么我们还要麻烦地将对象与空对象合并?因此,此代码执行相同的工作:

case actionsTypes.ADD_USER_SUCCESS:
    return [...state.filter(user => user.id !== action.payload.id), action.payload ];
  

可以吗?

case actionsTypes.EDIT_USER_SUCCESS:
    const indexOfUser = state.findIndex(user => user.id === action.payload.id);
    let newState = [...state];
    newState[indexOfUser] = action.payload;
    return newState;

对我来说似乎还可以。您无需直接改变状态,使用扩展语法来创建新的状态,更新相关部分,并最终使用此新状态来设置状态。

  

我不确定这个删除部分,确定还可以吗?   尝试在不删除用户的情况下返回状态?

case actionsTypes.DELETE_USER_SUCCESS:
    return [...state.filter(user => user.id !== action.user.id)];

再次,对我来说似乎还可以。您可以过滤已删除的用户并根据该状态更新您的状态。当然,还有其他情况需要考虑。例如,您是否有后端流程?您是否向数据库添加或删除用户?如果所有部分都为是,则需要确保后端过程成功,然后需要更新状态。但这是一个不同的话题。