7

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

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

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

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

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

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

4

2 回答 2

8

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

{
  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 中,我们需要创建entitiesreducer,它将监听所有动作,如果它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_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;
  }
}
于 2016-07-01T14:58:38.297 回答
3

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

(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'
    }
  }
}; 

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

于 2016-08-06T10:39:47.440 回答