在Redux存储中构建规范化的JSON响应并映射到React组件props

时间:2017-10-06 20:11:10

标签: json reactjs redux normalization

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

让我们从有效载荷的形状开始 -

{
      "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
      }
    }

规范化的有效负载是结构化的,以便顾问,机会,程序和潜在客户是兄弟姐妹,而不是祖先。它们都嵌套在“数据”中的一个层次。

然后,在我的“前景”缩减器中,我将前景状态初始化为具有以下键的对象:获取,失败和实体。前两个是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.ui中的this.props.entities和ui数据下。问题是我从我的潜在客户行动和减速器获取所有这些数据而不是单独的动作和减速器?我想在我的组件中减少访问器链,因为它变得非常容易出错并且难以阅读。使用单独的XHR和Reducer查询每个实体会更好吗?

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

1 个答案:

答案 0 :(得分:1)

摘要

我建议你重构你的状态:

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

为此,您需要为该州的每个密钥设置一个减速器。每个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,每个reducers处理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。这些缩减器中的每一个都将处理完全相同的动作。但他们只会处理他们关心的有效载荷部分。

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

作为旁注 - 假设我对单个API提供的有效负载是正确的 - 我会将您的操作常量重命名为REQUEST_PAYLOADRECEIVE_PAYLOAD和{{1}或者同样通用的东西。

还有一个建议:您可以将REQUEST_PAYLOAD_FAILUREfetching逻辑移动到failure,只需要管理API请求的成功/失败/加载。这样,你的每个其他reducer只需处理NetworkReducer个案,可以忽略其他行为。