如何避免 redux 中的重复代码(鸭子方法)?
How to avoid repetitive code in redux (ducks approach)?
我与 React
和 Redux
一起工作了大约 3 年。
我还使用 redux-thunk
来处理异步内容。
而且我非常喜欢它们,但最近我注意到我的项目中几乎所有的鸭子都使用相同的动作、缩减器、选择器等结构
例如 - 您有一个应用程序,它有一些用户和交易(或类似)列表、项目详细信息和编辑功能。
所有这些列表或项目都有自己的鸭子(动作、缩减器、选择器等)。
下面的代码将更清楚地显示问题:
// ACTIONS
const const setUser = user => ({
type: types.SET_USER,
payload: user,
});
const cleanUser = () => ({ type: types.CLEAN_USER });
const fetchUser = userId => dispatch =>
dispatch(fetchApi(userRequests.get(userId)))
.then(response => dispatch(setUser(response)))
.catch(error => showNotification(error));
// delete, update, etc... user actions
// REDUCER
const userReducer = (state = null, action) => {
switch (action.type) {
case types.SET_GROUP_ITEM:
return action.payload;
case types.CLEAN_GROUP_ITEM:
return null;
default:
return state;
}
};
上面的代码显示了来自 users duck
的 user
的结构,这对于其他鸭子来说几乎是相同的。
有什么方法可以减少重复代码吗?
感谢您的提前!
I noticed that almost all ducks in my project are using the same
structure of actions, reducers, selectors, etc.
我从未在 Redux 中实现 reducks 结构,但我确实在某一时刻发现自己在管理我的域实体时生成了 相同的 操作、reducer 等(例如人、订单、产品等)。
例如,我似乎总是关心:
- 我们目前正在获取实体吗?
isFetching
- 获取实体时是否有任何错误?
error
- 实体的实际数据是什么?
data
- 最后一次提取实体是什么时候?
lastUpdated
此外,域实体一直在添加,因此不断复制和粘贴 reducer/actions 并不理想。我们需要一种在 Redux 中 动态地 存储数据的方法,并且我们希望数据 始终 附加到 isFetching
和 lastUpdated
.
{
"entities": {
<SOME_ENTITY>: {
"isFetching" : null // Am I fetching?
"lastUpdated": null // When was I last fetched?
"data" : null // Here's my data!
"error" : null // Error during fetching
}
}
}
那么,如果我们发出一个带有将用作 Redux 中的键的字符串文字的操作(例如 products
、orders
)怎么办?这样,我们就可以发布我们可用的任何有效操作类型(FETCH_REQUEST
,等等),我们只需要更新 entity
键,它将自动分割出 space我们的商店:
dispatch({
entity : "products",
type : "FETCH_SUCCESS",
data : [{id: 1}],
lastUpdated: Date.now()
});
dispatch({
entity : "orders",
type : "FETCH_SUCCESS",
data : [{id: 2}, {id: 3}],
lastUpdated: Date.now()
});
结果状态
{
"entities": {
"products": {
"isFetching" : false,
"lastUpdated": 1526746314736,
"data" : [{id: 1}]
"error" : null
},
"orders": {
"isFetching" : false,
"lastUpdated": 1526746314943,
"data" : [{id: 2}, {id: 3}]
"error" : null
}
}
}
通用实体减速器
function entities (state = {}, action) {
switch (action.type) {
case FETCH_SUCCESS: // fall through
case FETCH_FAILURE: // fall through
case FETCH_REQUEST: {
return Object.assign({}, state, {
[action.entity]: entity(
state[action.entity],
action
)
});
}
default: {
return state;
}
}
};
实体减速器
const INITIAL_ENTITY_STATE = {
isFetching : false,
lastUpdated: null,
data : null,
error : null
};
function entity (state = INITIAL_ENTITY_STATE, action) {
switch (action.type) {
case FETCH_REQUEST: {
return Object.assign({}, state, {
isFetching: true,
error : null
});
}
case FETCH_SUCCESS: {
return Object.assign({}, state, {
isFetching : false,
lastUpdated: action.lastUpdated,
data : action.data,
error : null
});
}
case FETCH_FAILURE: {
return Object.assign({}, state, {
isFetching : false,
lastUpdated: action.lastUpdated,
data : null,
error : action.error
});
}
}
}
同样,通过使用通用的 reducer,我们可以动态地将我们想要的任何内容存储到 Redux 中,因为我们使用下面的 entity
字符串作为 Redux
中的键
dispatch({type: "FETCH_REQUEST", entity: "foo"});
dispatch({type: "FETCH_REQUEST", entity: "bar"});
dispatch({type: "FETCH_REQUEST", entity: "baz"});
结果状态
{
"entities": {
"foo": {
"isFetching": true,
"error": null,
"lastUpdated": null,
"data": null
},
"bar": {
"isFetching": true,
"error": null,
"lastUpdated": null,
"data": null
},
"baz": {
"isFetching": false,
"error": null,
"lastUpdated": null,
"data": null
}
}
}
如果这看起来很有趣,我确实写了一个小库(插件!),它完全符合上面描述的内容:
现场演示: http://mikechabot.github.io/react-boilerplate/dist/
就是说,我并没有推动那个库,我只是想描述我遇到的问题所采用的方法。您的操作集可能完全不同,在这种情况下,您仍然可以实现通用模式,但显然 reducer 的行为会有所不同。
我与 React
和 Redux
一起工作了大约 3 年。
我还使用 redux-thunk
来处理异步内容。
而且我非常喜欢它们,但最近我注意到我的项目中几乎所有的鸭子都使用相同的动作、缩减器、选择器等结构
例如 - 您有一个应用程序,它有一些用户和交易(或类似)列表、项目详细信息和编辑功能。 所有这些列表或项目都有自己的鸭子(动作、缩减器、选择器等)。
下面的代码将更清楚地显示问题:
// ACTIONS
const const setUser = user => ({
type: types.SET_USER,
payload: user,
});
const cleanUser = () => ({ type: types.CLEAN_USER });
const fetchUser = userId => dispatch =>
dispatch(fetchApi(userRequests.get(userId)))
.then(response => dispatch(setUser(response)))
.catch(error => showNotification(error));
// delete, update, etc... user actions
// REDUCER
const userReducer = (state = null, action) => {
switch (action.type) {
case types.SET_GROUP_ITEM:
return action.payload;
case types.CLEAN_GROUP_ITEM:
return null;
default:
return state;
}
};
上面的代码显示了来自 users duck
的 user
的结构,这对于其他鸭子来说几乎是相同的。
有什么方法可以减少重复代码吗? 感谢您的提前!
I noticed that almost all ducks in my project are using the same structure of actions, reducers, selectors, etc.
我从未在 Redux 中实现 reducks 结构,但我确实在某一时刻发现自己在管理我的域实体时生成了 相同的 操作、reducer 等(例如人、订单、产品等)。
例如,我似乎总是关心:
- 我们目前正在获取实体吗?
isFetching
- 获取实体时是否有任何错误?
error
- 实体的实际数据是什么?
data
- 最后一次提取实体是什么时候?
lastUpdated
此外,域实体一直在添加,因此不断复制和粘贴 reducer/actions 并不理想。我们需要一种在 Redux 中 动态地 存储数据的方法,并且我们希望数据 始终 附加到 isFetching
和 lastUpdated
.
{
"entities": {
<SOME_ENTITY>: {
"isFetching" : null // Am I fetching?
"lastUpdated": null // When was I last fetched?
"data" : null // Here's my data!
"error" : null // Error during fetching
}
}
}
那么,如果我们发出一个带有将用作 Redux 中的键的字符串文字的操作(例如 products
、orders
)怎么办?这样,我们就可以发布我们可用的任何有效操作类型(FETCH_REQUEST
,等等),我们只需要更新 entity
键,它将自动分割出 space我们的商店:
dispatch({
entity : "products",
type : "FETCH_SUCCESS",
data : [{id: 1}],
lastUpdated: Date.now()
});
dispatch({
entity : "orders",
type : "FETCH_SUCCESS",
data : [{id: 2}, {id: 3}],
lastUpdated: Date.now()
});
结果状态
{
"entities": {
"products": {
"isFetching" : false,
"lastUpdated": 1526746314736,
"data" : [{id: 1}]
"error" : null
},
"orders": {
"isFetching" : false,
"lastUpdated": 1526746314943,
"data" : [{id: 2}, {id: 3}]
"error" : null
}
}
}
通用实体减速器
function entities (state = {}, action) {
switch (action.type) {
case FETCH_SUCCESS: // fall through
case FETCH_FAILURE: // fall through
case FETCH_REQUEST: {
return Object.assign({}, state, {
[action.entity]: entity(
state[action.entity],
action
)
});
}
default: {
return state;
}
}
};
实体减速器
const INITIAL_ENTITY_STATE = {
isFetching : false,
lastUpdated: null,
data : null,
error : null
};
function entity (state = INITIAL_ENTITY_STATE, action) {
switch (action.type) {
case FETCH_REQUEST: {
return Object.assign({}, state, {
isFetching: true,
error : null
});
}
case FETCH_SUCCESS: {
return Object.assign({}, state, {
isFetching : false,
lastUpdated: action.lastUpdated,
data : action.data,
error : null
});
}
case FETCH_FAILURE: {
return Object.assign({}, state, {
isFetching : false,
lastUpdated: action.lastUpdated,
data : null,
error : action.error
});
}
}
}
同样,通过使用通用的 reducer,我们可以动态地将我们想要的任何内容存储到 Redux 中,因为我们使用下面的 entity
字符串作为 Redux
dispatch({type: "FETCH_REQUEST", entity: "foo"});
dispatch({type: "FETCH_REQUEST", entity: "bar"});
dispatch({type: "FETCH_REQUEST", entity: "baz"});
结果状态
{
"entities": {
"foo": {
"isFetching": true,
"error": null,
"lastUpdated": null,
"data": null
},
"bar": {
"isFetching": true,
"error": null,
"lastUpdated": null,
"data": null
},
"baz": {
"isFetching": false,
"error": null,
"lastUpdated": null,
"data": null
}
}
}
如果这看起来很有趣,我确实写了一个小库(插件!),它完全符合上面描述的内容:
现场演示: http://mikechabot.github.io/react-boilerplate/dist/
就是说,我并没有推动那个库,我只是想描述我遇到的问题所采用的方法。您的操作集可能完全不同,在这种情况下,您仍然可以实现通用模式,但显然 reducer 的行为会有所不同。