【问题标题】:Redux normalizr + dealing with reduced responsesRedux normalizr + 处理减少的响应
【发布时间】:2016-11-03 09:24:11
【问题描述】:

Normalizr 擅长创建实体的结构化 JSON 存储库。

我们有很多案例显示数据列表,例如posts 已标准化。在列出 posts 的地方,API 响应仅限于几个关键字段。

我们也有显示其中一个posts 的情况,尽管我们现在需要从 API 中获取包含所有字段的完整 JSON 实体。

如何最好地处理这个问题?

一个单独的reducer、thunk/saga、选择器和动作?

B 只需将从 API 获取的post 的扩展版本插入到减速器中。重用之前的选择器等?

【问题讨论】:

    标签: javascript reactjs redux api-design normalizr


    【解决方案1】:

    将应用的状态想象成一个数据库。我建议你使用这种状态形状:

    {
      entities: {
        // List of normalized posts without any nesting. No matter whether they have all fields or not.
        posts: {
          '1': {
            id: '1',
            title: 'Post 1',
          },
          '2': {
            id: '2',
            title: 'Post 2',
          }
        },
      },
      // Ids of posts, which need to displayed.
      posts: ['1', '2'],
      // Id of full post.
      post: '2',
    }
    

    首先,我们正在创建 normalizr 架构:

    // schemas.js
    import { Schema, arrayOf } from 'normalizr';
    
    const POST = new Schema('post');
    const POST_ARRAY = arrayOf(POST);
    

    成功响应后,我们正在规范化响应数据并调度操作:

    // actions.js/sagas.js
    function handlePostsResponse(body) {
      dispatch({
        type: 'FETCH_POSTS',
        payload: normalize(body.result, POST_ARRAY),
      });
    }
    
    function handleFullPostResponse(body) {
      dispatch({
        type: 'FETCH_FULL_POST',
        payload: normalize(body.result, POST),
      });
    }
    

    在 reducer 中,我们需要创建 entities reducer,它将监听所有操作,如果它在有效负载中有 entities 键,则会将此实体添加到应用状态:

    // reducers.js
    import merge from 'lodash/merge';
    
    function entities(state = {}, action) {
      const payload = action.payload;
    
      if (payload && payload.entities) {
        return merge({}, state, payload.entities);
      }
    
      return state;
    }
    

    我们还需要创建相应的reducer来处理FETCH_BOARDS和FETCH_FULL_BOARD动作:

    // Posts reducer will be storing only posts ids.
    function posts(state = [], action) {
      switch (action.type) {
        case 'FETCH_POSTS':
          // Post id is stored in `result` variable of normalizr output.
          return [...state, action.payload.result];
        default:
          return state;
      }
    }
    
    // Post reducer will be storing current post id.
    // Further, you can replace `state` variable by object and store `isFetching` and other variables.
    function post(state = null, action) {
      switch (action.type) {
        case 'FETCH_FULL_POST':
          return action.payload.id;
        default:
          return state;
      }
    }
    

    【讨论】:

    • 我有一个问题:merge({}, state, payload.entities); 会改变状态吗?
    • @Daskus 不,因为我们将空对象作为第一个参数传递,merge 函数将返回新对象。
    • 这是迄今为止最好的答案,我们最终完全采用了这种方法。关键在于编写好的选择器和过滤器。也强烈推荐使用 Immutable JS...!
    • 我遇到了这个配置的问题,state.entities 和 state.post 和 state.posts 最终可能会暂时不同步,这会导致重新选择中的临时映射(我在其中非规范化我的组件中的实体及其有序 ID),其中包含例如数组中未定义的条目帖子。例如state.posts 可以临时包含不在 state.entities.posts 中的 ID,如果 post reducer 没有运行而公共实体 reducer 已经运行。没有代码示例可能不清楚,但如果有人已经遇到过这个问题,他们会理解的。
    • 一切都会在之后立即自行解决,但是如果在第一次通过时提供一个具有未定义属性的数组(只是非常临时),一些组件可能会阻止。我正在弄清楚在哪里处理这个问题,这样它就不会吐出一个暂时无效的组件状态(例如,带有未定义选项的下拉值的数组),这似乎一定在我的选择器中,但感觉有点肮脏,因为它需要解决自身之外的问题(两种不同的事实来源,可能会暂时不同步)。有人有什么建议/指导吗?
    【解决方案2】:

    我同意您的两个选择,并且会得出相同的结论。但是让我们仔细看看它们,看看它们之间的优势:

    (B) 您可以将帖子实体(预览和完整表示)合并为减速器中的一个实体,但您会跟踪 result 数组(预览和完整表示),在 API 请求之后,您将从 normalizr 规范化数据中获得。然后,如果您已经拥有帖子的完整表示,那么您可以在之后轻松区分。您的子状态可能如下所示:

    const postState = {
      // merged results from PREVIEW api
      previews: [1, 2, 3],
    
      // merged results from FULL api
      full: [2],
    
      // all merged entities
      entities: {
        1: {
          title: 'foo1'
        },
        2: {
          title: 'foo2',
          body: 'bar',
        },
        3: {
          title: 'foo3'
        }
      }
    }; 
    

    (A) 您将有两个 reducer + 动作,每个表示一个,以区分实体。根据 PREVIEW 或 FULL posts API 请求,您将通过一项显式操作为您的减速器之一提供服务。您的子状态可能如下所示:

    const previewPostState = {
      // merged results from PREVIEW api
      result: [1, 2, 3],
    
      // all preview entities
      entities: {
        1: {
          title: 'foo1'
        },
        2: {
          title: 'foo2',
        },
        3: {
          title: 'foo3'
        }
      }
    }; 
    
    const fullPostState = {
      // merged results from FULL api
      result: [2],
    
      // all full entities
      entities: {
        2: {
          title: 'foo2',
          body: 'bar'
        }
      }
    }; 
    

    从一个非常高级的角度来看,您已经可以看到您必须保存重复的信息。带有id: 2 的帖子实体将使用其标题属性保存两次:一次用于previewPostState,一次用于fullPostState。一旦您想更改全局状态中的 title 属性,您必须在两个地方进行。这样做会违反 Redux 中的单一事实来源。这就是我选择 (B) 的原因:您的帖子实体只有一个位置,但可以通过结果数组清楚地区分它们的表示。

    【讨论】:

      猜你喜欢
      • 2016-07-23
      • 2016-11-03
      • 2019-03-05
      • 2016-05-07
      • 2019-03-24
      • 2023-03-03
      • 2023-03-20
      • 2020-11-29
      • 2021-11-14
      相关资源
      最近更新 更多