首页 > 解决方案 > React Redux - 使用 CRUD 的 Reducer - 最佳实践?

问题描述

我为实体编写了简单的 reducer User,现在我想在切换动作类型和返回状态时为其应用最佳实践。顺便提一下,我在单独的文件中提取了动作类型,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;
    }
}

标签: javascriptreactjsreduxreducers

解决方案


我不是一个经验丰富的开发人员,但让我回答你的问题,我到目前为止所学到的和遇到的。

第一个问题,是否必须为 FAILED 案例设置操作类型?比如添加GET_USERS_FAILED等并在usersReducer里面处理?

这不是强制性的,但如果您打算向您的客户提供反馈,那就太好了。例如,您启动了该GET_USERS过程,但它以某种方式失败。客户端没有任何反应,没有更新等。因此,您的客户不知道它失败了,并想知道为什么什么也没发生。但是,如果你有一个失败案例并且你发现了错误,你可以通知你的客户有一个错误。

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

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

case actionsTypes.GET_USERS_SUCCESS:
    return action.payload;

如果您通过单个请求获取整个用户,这没关系。这意味着你的 action.payload 是一个数组成为你的状态。但是,如果您不想通过单个请求(例如分页)获取所有用户,那么这还不够。您需要将您的状态与获取的状态连接起来。

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

在这里,我们使用扩展语法

显然,它传播了赋予它的内容:) 您可以以多种方式将它用于数组和对象。您可以查看文档。但这里有一些简单的例子。

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一个新对象并更改了它的年龄属性。在这两个例子中,我们从不改变我们的原始数据。

回到这里做什么?只是 action.payload 它只是单个用户对象?

case actionsTypes.GET_USER_SUCCESS:
    return ;

可能你不能直接在这个 reducer 中使用这个动作。因为您在这里的状态将您的用户保存为一个数组。您想以某种方式获得的用户做什么?假设您要保留“选定”用户。您可以为此创建一个单独的 reducer,或者在此处更改您的状态,使其成为一个对象并持有一个selectedUser属性并用它更新它。但是如果你改变你的 state 的形状,所有其他的 reducer 部分都需要改变,因为你的 state 将是这样的:

{
    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.payload 的用户,即正在添加的用户。这会返回一个数组,因此它会扩展它并将其与 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)];

再说一次,我觉得没问题。您过滤已删除的用户并据此更新您的状态。当然还有其他情况你应该考虑。例如,您是否有这些的后端流程?您是否在数据库中添加或删除用户?如果是所有部分,您需要确保后端流程成功,然后您需要更新您的状态。但我猜这是一个不同的话题。


推荐阅读