javascript - 使用通用动作和减速器简化 redux
问题描述
在 React-Redux 项目中,人们通常为每个连接的组件创建多个 action 和 reducer。但是,这会为简单的数据更新创建大量代码。
使用单个通用操作和缩减程序来封装所有数据更改以简化和加快应用程序开发是否是一种好习惯。
使用这种方法会有什么缺点或性能损失。因为我认为没有明显的权衡,它使开发变得更加容易,我们可以将它们全部放在一个文件中!这种架构的例子:
// Say we're in user.js, User page
// state
var initialState = {};
// generic action --> we only need to write ONE DISPATCHER
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer --> we only need to write ONE ACTION REDUCER
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// define component
var User = React.createClass({
render: function(){
// Here's the magic...
// We can just call the generic setState() to update any data.
// No need to create separate dispatchers and reducers,
// thus greatly simplifying and fasten app development.
return [
<div onClick={() => setState({ someField: 1 })}/>,
<div onClick={() => setState({ someOtherField: 2, randomField: 3 })}/>,
<div onClick={() => setState({ orJustAnything: [1,2,3] })}/>
]
}
});
// register component for data update
function mapStateToProps(state){
return { ...state.user };
}
export default connect(mapStateToProps)(User);
编辑
所以典型的 Redux 架构建议创建:
- 包含所有操作的集中文件
- 包含所有减速器的集中文件
问题是,为什么是两步过程?这是另一个架构建议:
创建1 组文件,其中包含setXField()
处理所有数据更改的所有文件。而其他组件只是使用它们来触发更改。简单的。例子:
/** UserAPI.js
* Containing all methods for User.
* Other components can just call them.
*/
// state
var initialState = {};
// generic action
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// API that we export
let UserAPI = {};
// set user name
UserAPI.setName = function(name){
$.post('/user/name', { name }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ name });
});
};
// set user picture URL
UserAPI.setPicture = function(url){
$.post('/user/picture', { url }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ url });
});
};
// logout, clear user
UserAPI.logout = function(){
$.post('/logout', {}, function(){
setState(initialState);
});
};
// Etc, you got the idea...
// Moreover, you can add a bunch of other User related methods,
// like some helper methods unrelated to Redux, or Ajax getters.
// Now you have everything related to User available in a single file!
// It becomes much easier to read through and understand.
// Finally, you can export a single UserAPI object, so other
// components only need to import it once.
export default UserAPI
请仔细阅读上面代码部分中的注释。
现在而不是拥有一堆动作/调度程序/减速器。您有1 个文件,其中包含 User 概念所需的所有内容。为什么这是一个不好的做法?IMO,它使程序员的生活变得更加轻松,其他程序员只需从上到下阅读文件即可了解业务逻辑,无需在 action/reducer 文件之间来回切换。哎呀,甚至redux-thunk
不需要!您甚至可以一一测试功能。所以可测试性不会丢失。
解决方案
首先,与其调用store.dispatch
您的动作创建者,不如返回一个对象(动作),从而简化测试并启用服务器渲染。
const setState = (obj) => ({
type: 'SET_USER',
data: obj
})
onClick={() => this.props.setState(...)}
// bind the action creator to the dispatcher
connect(mapStateToProps, { setState })(User)
您还应该使用ES6 类而不是React.createClass
.
回到主题,一个更专业的动作创建者应该是这样的:
const setSomeField = value => ({
type: 'SET_SOME_FIELD',
value,
});
...
case 'SET_SOME_FIELD':
return { ...state, someField: action.value };
这种方法相对于您的通用方法的优势
1. 更高的可重用性
如果someField
在多个地方设置,则调用setSomeField(someValue)
比setState({ someField: someValue })}
.
2. 更高的可测试性
您可以轻松测试setSomeField
以确保它仅正确更改相关状态。
使用 generic setState
,您也可以进行测试setState({ someField: someValue })}
,但不能直接保证您的所有代码都会正确调用它。
例如。您团队中的某个人可能会打错字然后打电话setState({ someFeild: someValue })}
。
结论
缺点并不明显,因此如果您认为值得为您的项目进行权衡,那么使用通用动作创建器来减少专用动作创建器的数量是完全可以的。
编辑
关于您将减速器和操作放在同一个文件中的建议:通常最好将它们放在单独的文件中以实现模块化;这是一个一般原则,并非 React 独有。
但是,您可以将相关的 reducer 和 action 文件放在同一个文件夹中,这可能会更好/更差,具体取决于您的项目要求。有关一些背景信息,请参见this和this。
您还需要userReducer
为您的根减速器导出,除非您使用通常不推荐的多个商店。
推荐阅读
- c - c语言中的web cgi,提供二进制图像文件
- python - ModuleNotFoundError:没有名为“psycopg2”的模块(但已安装)
- java - 哪个更可能用于 KISS?
- python - 如何计算平均 TPR、TNR、FPR、FNR - 多类分类
- android - 使用导航抽屉创建一个简单的 Android 片段
- php - 当数据库中存在记录时,尝试 PDO 包装器并将结果设为“NULL”
- matlab - MATLAB 上用于分叉的并行池化
- python - 一个简单的python程序的时间复杂度
- java - Java OutOfMemoryError:奇怪的行为
- ios - 可选类型“UITextField?”的值 必须解包以引用已包装基本类型“UITextField”的成员“文本”