javascript - 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;
}
}
解决方案
我不是一个经验丰富的开发人员,但让我回答你的问题,我到目前为止所学到的和遇到的。
第一个问题,是否必须为 FAILED 案例设置操作类型?比如添加GET_USERS_FAILED等并在usersReducer里面处理?
这不是强制性的,但如果您打算向您的客户提供反馈,那就太好了。例如,您启动了该GET_USERS
过程,但它以某种方式失败。客户端没有任何反应,没有更新等。因此,您的客户不知道它失败了,并想知道为什么什么也没发生。但是,如果你有一个失败案例并且你发现了错误,你可以通知你的客户有一个错误。
为此,您可以使用GET_USERS_FAILED
两种方式的操作类型,例如。一个在你的userReducers
,一个用于,比如说,一个error
或feedback
减速器。第一个返回状态,因为您的过程失败并且您无法获得所需的数据,因此无论如何都不想改变状态。第二个更新您的反馈减少器并可以更改状态,假设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)];
再说一次,我觉得没问题。您过滤已删除的用户并据此更新您的状态。当然还有其他情况你应该考虑。例如,您是否有这些的后端流程?您是否在数据库中添加或删除用户?如果是所有部分,您需要确保后端流程成功,然后您需要更新您的状态。但我猜这是一个不同的话题。
推荐阅读
- javascript - 为什么启用计时器自动启动会禁用我的停止和重置按钮?
- javascript - MyClass 类型上不存在属性 myProp:Typescript
- flutter - 当我 Ctrl+S 时,为什么我的 Dart 代码会自动移动到不同的行?
- vb.net - 函数的VB动态参数
- python - 如何避免返回集的 read_sql_query 数据截断
- python - 如何在多个图像输入中使用 Conv2D?
- android - 我想从数组转换
到数组 ? - java - 如何将 spring bean 绑定到 JNDI 以便可以从不同的战争中访问它?
- vb.net - 如何在 VB.NET Web 应用程序上为列表框启用多列?
- javascript - 包装不同大小的 div,没有垂直空格