如何在 react-redux 中重用 action creator?
How to reuse action creators in react-redux?
我的问题是关于如何构造 reducer 和 action creator 以便正确重用它们。
我在网上阅读了大量关于 reducer 组合和高阶 reducer 的参考书目,并通过创建命名空间化的 reducer factory/generator 设法朝着正确的方向迈进了一些步骤。有了这个,我可以拥有具有独立状态的相同 component/view 的不同实例,它们具有共同的行为。然而,对于具有一些共同点但不相等的 components/views 来说,情况并非如此。比方说...实体的显示和编辑视图。
在安装时,这两个组件都需要以相同的方式从 API 中获取实体数据,但是显示组件的功能比编辑组件少得多,编辑组件还处理表单提交,处理错误等...
所以,话虽如此...我应该如何扩展 editEntityReducer 和 editEntity 操作创建器以包括 entityReducer 和实体操作创建器以及编辑自己的减速器功能和操作创建器?
这是我目前所拥有的,以用户实体为例:
User reducer + action creators (user.js):
import normalize from 'jsonapi-normalizer'
import { api, authenticatedHeaders } from 'api'
import { RSAA } from 'redux-api-middleware'
import { List, Record } from 'immutable'
import * as constants from './constants'
// ------------------------------------
// Actions
// ------------------------------------
export const destroyUser = (userId) => {
// Uses redux-api-middleware. see: https://github.com/agraboso/redux-api-middleware
return {
[RSAA]: {
endpoint: api.users.destroy.path(userId),
method: api.users.destroy.method,
headers: (state) => authenticatedHeaders(state.session.authorization.token),
types: [
constants.DESTROY_START,
constants.DESTROY_SUCCESS,
constants.DESTROY_FAIL]
}
}
}
export const fetchUser = (userId) => {
// Uses redux-api-middleware. see: https://github.com/agraboso/redux-api-middleware
return {
[RSAA]: {
endpoint: api.users.show.path(userId),
method: api.users.show.method,
headers: (state) => authenticatedHeaders(state.session.authorization.token),
types: [
constants.FETCH_START,
{
type: constants.FETCH_SUCCESS,
payload: (action, state, res) => {
return res.json().then(json => normalize(json))
}
},
constants.FETCH_FAIL]
}
}
}
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = (prefix) => {
return {
[`${prefix}_${constants.DESTROY_START}`]: (state, action) => {
return state.set('destroying', true)
},
[`${prefix}_${constants.DESTROY_SUCCESS}`]: (state, action) => {
return state.set('destroying', false)
},
[`${prefix}_${constants.DESTROY_FAIL}`]: (state, action) => {
return state.set('destroying', false)
},
[`${prefix}_${constants.FETCH_START}`]: (state, action) => {
return state.set('loading', true)
},
[`${prefix}_${constants.FETCH_SUCCESS}`]: (state, { payload }) => {
const users = payload.entities.user
const userIds = payload.result.user
const roles = payload.entities.role
// It's a single record fetch
const user = users[userIds[0]]
return state.merge({
loading: false,
record: Record({ user: Record(user)(), roles: Record(roles)() })()
})
},
[`${prefix}_${constants.FETCH_FAIL}`]: (state, action) => {
return state.set('loading', false)
}
}
}
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = Record({
destroying: false,
loading: true, // initially true so will only go to false upong user loaded
record: Record({ user: Record({})(), roles: List([]) })()
})()
const userReducer = (prefix = 'USER') => {
if (prefix === undefined || prefix.length < 1) {
throw new Error('prefix must be defined')
}
return (state = initialState, action) => {
const handler = ACTION_HANDLERS(prefix)[`${prefix}_${action.type}`]
return handler ? handler(state, action) : state
}
}
export default userReducer
编辑用户 reducer 和 action creators (edit_user.js):
import normalize from 'jsonapi-normalizer'
import { api, authenticatedHeaders } from 'api'
import { RSAA } from 'redux-api-middleware'
import { List, Record } from 'immutable'
import * as constants from './constants'
// ------------------------------------
// Actions
// ------------------------------------
export const updateUser = (userId, params = {}) => {
// Uses redux-api-middleware. see: https://github.com/agraboso/redux-api-middleware
return {
[RSAA]: {
endpoint: api.users.update.path(userId),
method: api.users.update.method,
headers: (state) => authenticatedHeaders(state.session.authorization.token),
types: [
constants.USER_UPDATE_START,
constants.USER_UPDATE_SUCCESS,
constants.USER_UPDATE_FAIL]
}
}
}
// TODO: see how to reuse this from the user.js file!
export const fetchUser = (userId) => {
// Uses redux-api-middleware. see: https://github.com/agraboso/redux-api-middleware
return {
[RSAA]: {
endpoint: api.users.show.path(userId),
method: api.users.show.method,
headers: (state) => authenticatedHeaders(state.session.authorization.token),
types: [
constants.USER_FETCH_START,
{
type: constants.USER_FETCH_SUCCESS,
payload: (action, state, res) => {
return res.json().then(json => normalize(json))
}
},
constants.USER_FETCH_FAIL]
}
}
}
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[constants.USER_UPDATE_START]: (state, action) => {
return state.set('loading', true)
},
[constants.USER_UPDATE_SUCCESS]: (state, action) => {
return state.set('loading', false)
},
[constants.USER_UPDATE_FAIL]: (state, action) => {
return state.set('loading', false)
},
// TODO: this reducers are the same as user.js, reuse them!!
[constants.USER_FETCH_START]: (state, action) => {
return state.set('loading', true)
},
[constants.USER_FETCH_SUCCESS]: (state, { payload }) => {
const users = payload.entities.user
const userIds = payload.result.user
const roles = payload.entities.role
// It's a single record fetch
const user = users[userIds[0]]
return state.merge({
loading: false,
record: Record({ user: Record(user)(), roles: Record(roles)() })()
})
},
[constants.USER_FETCH_FAIL]: (state, action) => {
return state.set('loading', false)
}
}
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = Record({
loading: true, // initially true so will only go to false upong user loaded
record: Record({ user: Record({})(), roles: List([]) })()
})()
export default function editUserReducer (state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]
return handler ? handler(state, action) : state
}
正如您在代码中的 TODO 中看到的那样,我希望能够重用 reducer 和 action creator 的那些部分,因为它不仅可重用于实体基本操作,而且可重用于任何资源上的任何通用 CRUD 操作我的应用程序可能会用到!
谢谢
您可以创建一个新函数,并将差异(据我所见,常量)提取到参数中,作为高阶函数(就像我在下面所做的那样),或者将它们与现有的结合起来参数 (userId
):
export const createFetchUser = (fetchStart, fetchSuccess, fetchFail) => userId =>
// Uses redux-api-middleware. see: https://github.com/agraboso/redux-api-middleware
({
[RSAA]: {
endpoint: api.users.show.path(userId),
method: api.users.show.method,
headers: state => authenticatedHeaders(state.session.authorization.token),
types: [
fetchStart,
{
type: fetchSuccess,
payload: (action, state, res) => res.json().then(json => normalize(json)),
},
fetchFail,
],
},
});
然后您可以在 user.js
和 edit_user.js
中导入此函数,为不同的常量创建 fetchUser
函数,例如。 user.js
:
export const fetchUser = userId =>
createFetchUser(constants.FETCH_START, constants.FETCH_SUCCESS, constants.FETCH_FAIL);
您可以为减速器做类似的事情。
我的问题是关于如何构造 reducer 和 action creator 以便正确重用它们。 我在网上阅读了大量关于 reducer 组合和高阶 reducer 的参考书目,并通过创建命名空间化的 reducer factory/generator 设法朝着正确的方向迈进了一些步骤。有了这个,我可以拥有具有独立状态的相同 component/view 的不同实例,它们具有共同的行为。然而,对于具有一些共同点但不相等的 components/views 来说,情况并非如此。比方说...实体的显示和编辑视图。
在安装时,这两个组件都需要以相同的方式从 API 中获取实体数据,但是显示组件的功能比编辑组件少得多,编辑组件还处理表单提交,处理错误等...
所以,话虽如此...我应该如何扩展 editEntityReducer 和 editEntity 操作创建器以包括 entityReducer 和实体操作创建器以及编辑自己的减速器功能和操作创建器?
这是我目前所拥有的,以用户实体为例:
User reducer + action creators (user.js):
import normalize from 'jsonapi-normalizer'
import { api, authenticatedHeaders } from 'api'
import { RSAA } from 'redux-api-middleware'
import { List, Record } from 'immutable'
import * as constants from './constants'
// ------------------------------------
// Actions
// ------------------------------------
export const destroyUser = (userId) => {
// Uses redux-api-middleware. see: https://github.com/agraboso/redux-api-middleware
return {
[RSAA]: {
endpoint: api.users.destroy.path(userId),
method: api.users.destroy.method,
headers: (state) => authenticatedHeaders(state.session.authorization.token),
types: [
constants.DESTROY_START,
constants.DESTROY_SUCCESS,
constants.DESTROY_FAIL]
}
}
}
export const fetchUser = (userId) => {
// Uses redux-api-middleware. see: https://github.com/agraboso/redux-api-middleware
return {
[RSAA]: {
endpoint: api.users.show.path(userId),
method: api.users.show.method,
headers: (state) => authenticatedHeaders(state.session.authorization.token),
types: [
constants.FETCH_START,
{
type: constants.FETCH_SUCCESS,
payload: (action, state, res) => {
return res.json().then(json => normalize(json))
}
},
constants.FETCH_FAIL]
}
}
}
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = (prefix) => {
return {
[`${prefix}_${constants.DESTROY_START}`]: (state, action) => {
return state.set('destroying', true)
},
[`${prefix}_${constants.DESTROY_SUCCESS}`]: (state, action) => {
return state.set('destroying', false)
},
[`${prefix}_${constants.DESTROY_FAIL}`]: (state, action) => {
return state.set('destroying', false)
},
[`${prefix}_${constants.FETCH_START}`]: (state, action) => {
return state.set('loading', true)
},
[`${prefix}_${constants.FETCH_SUCCESS}`]: (state, { payload }) => {
const users = payload.entities.user
const userIds = payload.result.user
const roles = payload.entities.role
// It's a single record fetch
const user = users[userIds[0]]
return state.merge({
loading: false,
record: Record({ user: Record(user)(), roles: Record(roles)() })()
})
},
[`${prefix}_${constants.FETCH_FAIL}`]: (state, action) => {
return state.set('loading', false)
}
}
}
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = Record({
destroying: false,
loading: true, // initially true so will only go to false upong user loaded
record: Record({ user: Record({})(), roles: List([]) })()
})()
const userReducer = (prefix = 'USER') => {
if (prefix === undefined || prefix.length < 1) {
throw new Error('prefix must be defined')
}
return (state = initialState, action) => {
const handler = ACTION_HANDLERS(prefix)[`${prefix}_${action.type}`]
return handler ? handler(state, action) : state
}
}
export default userReducer
编辑用户 reducer 和 action creators (edit_user.js):
import normalize from 'jsonapi-normalizer'
import { api, authenticatedHeaders } from 'api'
import { RSAA } from 'redux-api-middleware'
import { List, Record } from 'immutable'
import * as constants from './constants'
// ------------------------------------
// Actions
// ------------------------------------
export const updateUser = (userId, params = {}) => {
// Uses redux-api-middleware. see: https://github.com/agraboso/redux-api-middleware
return {
[RSAA]: {
endpoint: api.users.update.path(userId),
method: api.users.update.method,
headers: (state) => authenticatedHeaders(state.session.authorization.token),
types: [
constants.USER_UPDATE_START,
constants.USER_UPDATE_SUCCESS,
constants.USER_UPDATE_FAIL]
}
}
}
// TODO: see how to reuse this from the user.js file!
export const fetchUser = (userId) => {
// Uses redux-api-middleware. see: https://github.com/agraboso/redux-api-middleware
return {
[RSAA]: {
endpoint: api.users.show.path(userId),
method: api.users.show.method,
headers: (state) => authenticatedHeaders(state.session.authorization.token),
types: [
constants.USER_FETCH_START,
{
type: constants.USER_FETCH_SUCCESS,
payload: (action, state, res) => {
return res.json().then(json => normalize(json))
}
},
constants.USER_FETCH_FAIL]
}
}
}
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[constants.USER_UPDATE_START]: (state, action) => {
return state.set('loading', true)
},
[constants.USER_UPDATE_SUCCESS]: (state, action) => {
return state.set('loading', false)
},
[constants.USER_UPDATE_FAIL]: (state, action) => {
return state.set('loading', false)
},
// TODO: this reducers are the same as user.js, reuse them!!
[constants.USER_FETCH_START]: (state, action) => {
return state.set('loading', true)
},
[constants.USER_FETCH_SUCCESS]: (state, { payload }) => {
const users = payload.entities.user
const userIds = payload.result.user
const roles = payload.entities.role
// It's a single record fetch
const user = users[userIds[0]]
return state.merge({
loading: false,
record: Record({ user: Record(user)(), roles: Record(roles)() })()
})
},
[constants.USER_FETCH_FAIL]: (state, action) => {
return state.set('loading', false)
}
}
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = Record({
loading: true, // initially true so will only go to false upong user loaded
record: Record({ user: Record({})(), roles: List([]) })()
})()
export default function editUserReducer (state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]
return handler ? handler(state, action) : state
}
正如您在代码中的 TODO 中看到的那样,我希望能够重用 reducer 和 action creator 的那些部分,因为它不仅可重用于实体基本操作,而且可重用于任何资源上的任何通用 CRUD 操作我的应用程序可能会用到!
谢谢
您可以创建一个新函数,并将差异(据我所见,常量)提取到参数中,作为高阶函数(就像我在下面所做的那样),或者将它们与现有的结合起来参数 (userId
):
export const createFetchUser = (fetchStart, fetchSuccess, fetchFail) => userId =>
// Uses redux-api-middleware. see: https://github.com/agraboso/redux-api-middleware
({
[RSAA]: {
endpoint: api.users.show.path(userId),
method: api.users.show.method,
headers: state => authenticatedHeaders(state.session.authorization.token),
types: [
fetchStart,
{
type: fetchSuccess,
payload: (action, state, res) => res.json().then(json => normalize(json)),
},
fetchFail,
],
},
});
然后您可以在 user.js
和 edit_user.js
中导入此函数,为不同的常量创建 fetchUser
函数,例如。 user.js
:
export const fetchUser = userId =>
createFetchUser(constants.FETCH_START, constants.FETCH_SUCCESS, constants.FETCH_FAIL);
您可以为减速器做类似的事情。