【问题标题】:How to reuse action creators in react-redux?如何在 react-redux 中重用动作创建者?
【发布时间】:2018-05-06 03:11:48
【问题描述】:

我的问题是关于如何构建 reducer 和 action creators 以便正确重用它们。 我在网上阅读了大量关于减速器组合和高阶减速器的参考书目,并通过创建命名空间的减速器工厂/生成器来设法朝着正确的方向迈进。有了这个,我可以拥有具有独立状态的相同组件/视图的不同实例,它们具有共同的行为。然而,对于具有一些共同点但不相等的组件/视图来说,情况并非如此。可以说...实体的显示和编辑视图。

在挂载时,这两个组件都需要以相同的方式从 API 获取实体数据,但是显示组件的功能比编辑组件少得多,后者还处理表单提交、处理错误等...

那么,话虽如此...我应该如何扩展 editEntityReducer 和 editEntity 动作创建者以包括 entityReducer 和实体动作创建者以及编辑自己的减速器功能和动作创建者?

这是我目前所拥有的,以用户实体为例:

用户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 操作应用程序可以使用!

谢谢

【问题讨论】:

    标签: reactjs redux higher-order-functions reducers


    【解决方案1】:

    您可以创建一个新函数并将差异(从我所看到的常量)提取到参数中,或者作为高阶函数(就像我在下面所做的那样),或者将它们与现有参数( 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.jsedit_user.js 中导入此函数,为不同的常量创建 fetchUser 函数,例如。对于user.js

    export const fetchUser = userId =>
      createFetchUser(constants.FETCH_START, constants.FETCH_SUCCESS, constants.FETCH_FAIL);
    

    您可以为减速器做类似的事情。

    【讨论】:

    • 我最终使用了这个:redux.js.org/recipes/structuring-reducers/reusing-reducer-logic + 混合了你的答案,通过使用前缀而不是将每个动作类型(fetchStart、fetchSuccess 等)传递给动作创建者。我会接受你的回答,但是最好有某种结构来与动作创建者前缀共享减速器前缀,而不必在调用动作创建者时将其作为参数传递给它们
    猜你喜欢
    • 2016-09-22
    • 2017-08-02
    • 1970-01-01
    • 2020-12-05
    • 1970-01-01
    • 1970-01-01
    • 2020-02-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多