【问题标题】:Structuring Normalized JSON response in Redux store and mapping to React component props在 Redux 存储中构建规范化 JSON 响应并映射到 React 组件道具
【发布时间】:2017-10-06 20:11:10
【问题描述】:

就在最近,我们的团队开始以标准化方式构建我们的 JSON 有效负载。我最习惯于在 React 组件甚至 reducer 中处理嵌套数据,但我看到了这里的好处(更少的连接组件重新渲染、简化的 reducer 代码和更容易的测试),我很高兴开始使用这种方法。但是,在我第一次尝试后,我确实对状态形状有些困惑。

让我们从payload的形状开始-

{
      "data": {
        "advisors": {
          "allIds": [
            2
          ], 
          "byId": {
            "2": {
              "active": true, 
              "avatar_url": null, 
              "country": "US", 
              "email": "demo@gmail.com", 
              "first_name": "George Michael", 
              "full_name": "George Michael Bluth", 
              "id": 2, 
              "last_name": "Bluth", 
              "time_zone": "US/Central"
            }
          }
        }, 
        "opportunities": {
          "allIds": [
            "100-3", 
          ], 
          "byId": {
            "100-3": {
              "created": "Fri, 29 Sep 2017 20:00:40 GMT", 
              "program_id": 3, 
              "prospect_id": 100
            }
          }
        }, 
        "programs": {
          "allIds": [ 
            3
          ], 
          "byId": {
            "3": {
              "abbr": "CAP", 
              "end_date": null, 
              "funnel_id": 2, 
              "id": 3, 
              "launch_date": "Sat, 11 Mar 2017 00:00:00 GMT", 
              "name": "Certificate in Astral Projection", 
              "period_end": null, 
              "period_start": null, 
              "program_level_abbr": "NCC", 
              "school_id": 2, 
              "virtual": false
            }
          }
        }, 
        "prospects": {
          "allIds": [
            2,
          ], 
          "byId": {
            "2": {
              "advisor_id": 3, 
              "contact_attempt_count": 0, 
              "contact_success_count": 0, 
              "do_not_call": false, 
              "do_not_email": false, 
              "do_not_mail": false, 
              "email": "adavis.est@hotmail.com", 
              "first_name": "Antonio", 
              "id": 2, 
              "inactive": false, 
              "last_name": "Davis", 
              "phone": {
                "area_code": "800", 
                "extension": "70444", 
                "number": "3575792"
              }, 
              "priority": 10.0, 
              "referred_by_prospect_id": null, 
              "third_party": false
            },
          }
        }
      }, 
      "pagination": {
        "page_number": 1, 
        "total": 251
      }
    }

标准化负载的结构使得顾问、机会、计划和潜在客户是兄弟姐妹而不是祖先。它们都嵌套在“数据”中的一层。

然后,在我的“潜在客户”reducer 中,我将潜在客户状态初始化为具有以下键的对象:获取、失败和实体。前两个是 UI 数据,实体将容纳响应(顾问、机会、计划和潜在客户)。

const initialState = {
  fetching: false,
  failure: false,
  entities: null,
};

function prospects(state = initialState, action) {
  switch (action.type) {
    case constants.prospects.REQUEST_PROSPECTS:
      return { ...state, fetching: true };
    case constants.prospects.RECEIVE_PROSPECTS:
      return Object.assign({}, state, {
        fetching: false,
        entities: action.data,
      });
    case constants.prospects.REQUEST_PROSPECTS_FAILURE:
      return { ...state, fetching: false, failure: true };
    default:
      return state;
  }
}

现在让我来到这里的危险信号 - 我的道具和内部组件状态似乎结构奇怪。我像这样 mapStateToProps:

const mapStateToProps = state => ({
  prospects: state.prospects,
});

这导致我接触到这样的顾问、机会、计划和潜在客户:

this.props.fetching
this.props.failure
this.props.prospects.entities.advisors.allIds.length
this.props.prospects.entities.opportunities.allIds.length
this.props.prospects.entities.programs.allIds.length
this.props.prospects.entities.prospects.allIds.length

我的理解是,通过标准化方法,事物通常位于 this.props.entities 下,ui 数据位于 this.props.ui 中。问题是我从潜在客户 action 和 reducer 中获取所有这些数据,而不是单独的 action 和 reducer?我想减少组件中的访问器链,因为它变得非常容易出错且难以阅读。使用单独的 XHR 和 reducer 查询每个实体会更好吗?

我知道有很多关于这种方法的好资源,包括来自 DA 的视频。但是我还没有找到所有这些问题的答案。谢谢!

【问题讨论】:

    标签: json reactjs redux normalization


    【解决方案1】:

    总结

    我建议您将状态重构为:

    {
      network: {
        loading: false,
        failure: false
      },
      advisors: { allIds, byId },
      opportunities: { allIds, byId },
      programs: { allIds, byId },
      prospects: { allIds, byId },
    }
    

    为此,您需要为状态中的每个键使用一个 reducer。每个 reducer 将处理其规范化有效负载的部分,否则忽略操作。

    减速器

    Network.js:

    function network(state = { loading: false, failure: false }, action) {
      switch (action.type) {
        case constants.REQUEST_PAYLOAD:
          return { ...state, fetching: true };
        case constants.RECEIVE_PAYLOAD:
          return { ...state, fetching: false, failure: false };
        case constants.prospects.REQUEST_PROSPECTS_FAILURE:
          return { ...state, fetching: false, failure: true };
        default:
          return state;
      }
    }
    

    Prospects.js:

    function prospects(state = { allIds: [], byId: {} }, action) {
      switch (action.type) {
        case constants.RECEIVE_PAYLOAD:
          // depending on your use case, you may need to merge the existing
          // allIds and byId with the action's. This would allow you to 
          // issue the request multiple times and add to the store instead
          // of overwriting it each time.
          return { ...state, ...action.data.prospects };
        default:
          return state;
      }
    }
    

    对状态的每个其他部分重复前景化简器。


    注意

    我假设您的负载以这种方式从单个 API 调用中返回,并且您不会将每个兄弟(顾问、机会、程序和潜在客户)的单独调用拼接在一起。


    详情

    为了将您的有效负载存储在存储中,我建议编写单独的 reducer,每个 reducer 处理 API 调用返回的状态的不同部分。

    对于prospects,您应该存储有效负载的prospects 部分并丢弃其余部分。

    所以不是...

    case constants.prospects.RECEIVE_PROSPECTS:
      return Object.assign({}, state, {
        fetching: false,
        entities: action.data,
      });
    

    你应该这样做......

    case constants.prospects.RECEIVE_PROSPECTS:
      return { 
        ...state,
        fetching: false,
        entities: action.data.prospects,
      };
    

    然后为您的 API 调用返回的每种其他类型的数据设置一个类似的 reducer。这些 reducer 中的每一个都将处理完全相同的操作。不过,他们只会处理他们关心的有效负载部分。

    最后,在您的mapStateToProps 中,state.prospects 将只包含潜在客户数据。

    作为旁注——假设我对单个 API 传递的有效负载是正确的——我会将您的操作常量重命名为 REQUEST_PAYLOADRECEIVE_PAYLOADREQUEST_PAYLOAD_FAILURE,或其他类似的通用名称。

    还有一个建议:您可以将您的fetchingfailure 逻辑移动到一个NetworkReducer 中,该NetworkReducer 只负责管理API 请求的成功/失败/加载。这样一来,您的每个其他 reducer 只需处理 RECEIVE 情况,就可以忽略其他操作。

    【讨论】:

    • 你给了我我想要的东西。非常感谢。已经习惯了非规范化方法、嵌套数据和每个 API 调用一个 reducer,我看不出一个 reducer 是我的问题。我会接受你所有的建议!但是,我确实有一些小疑问:
    • 您应该在 API 之后命名操作。如果您有多个 API 调用,请针对每个调用定制操作。
    • 网络减速器取决于您的具体情况。如果您在任何给定时间只执行一个网络调用,那么仅存储当前请求的数据是合理的。如果您将并行执行调用,那么您可能想要执行 A. 保留所有请求的历史记录或 B. 保留请求类型到网络状态的映射。 ?
    • 命名是出了名的困难。我不会太挂断它。如果您将所有这些限定为“潜在客户数据”,即使并非所有这些都是严格意义上的“潜在客户”,那么 REQUEST_PROSPECT_DATA 可能就可以了。
    • 很高兴为您提供帮助! ?
    猜你喜欢
    • 2020-02-04
    • 1970-01-01
    • 2017-11-07
    • 2019-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-24
    • 1970-01-01
    相关资源
    最近更新 更多