首页 文章

Redux normalizr处理减少的响应

提问于
浏览
5

Normalizr非常擅长创建实体的结构化JSON存储库 .

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

我们还有一些情况,我们显示其中一个 posts 虽然我们现在需要从API获取具有所有字段的FULL JSON实体 .

如何处理这个问题最好?

A 一个单独的减速器,thunk / saga,选择器和动作?

B 只需将从API中提取的 post 的扩展版本插入到reducer中 . 从以前重用选择器等?

2 回答

  • 2

    将应用程序的状态视为数据库 . 我建议你使用这种状态形状:

    {
      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),
      });
    }
    

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

    // 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;
    }
    

    我们还需要创建相应的reducers来处理 FETCH_BOARDSFETCH_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;
      }
    }
    
  • 6

    我同意你们的两个选择,并且会得出同样的结论 . 但是让我们仔细看看它们,看一个优势形成另一个:

    (B) 您可以将后期实体(预览和完整表示)合并为reducer中的一个实体,但是您将跟踪 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请求,您可以通过一个显式操作为其中一个Reducer提供服务 . 您的子状态可能如下所示:

    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 的post实体将使用其title属性保存两次:一次为 previewPostState ,一次为 fullPostState . 想要在全局状态下更改title属性后,必须在两个位置执行此操作 . 人们会违反Redux的单一事实来源 . 这就是我选择 (B) 的原因:你的帖子实体有一个位置,但可以通过结果数组清楚地区分它们的表示 .

相关问题