Redux normalizr + 处理减少的响应
Redux normalizr + dealing with reduced responses
Normalizr 非常擅长创建结构化的 JSON 实体存储库。
我们有很多案例显示数据列表,例如posts
已标准化。在列出 posts
的地方,API 响应仅限于几个关键字段。
我们也有显示其中一个 posts
的情况,尽管我们现在需要从 API 中获取包含所有字段的完整 JSON 实体。
如何最好地处理这个问题?
A 一个单独的减速器,thunk/saga,选择器和动作?
B 只需将从 API 获取的 post
的扩展版本插入减速器。重用之前的选择器等?
将应用程序的状态想象成一个数据库。我建议你使用这个状态形状:
{
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;
}
}
我同意你的两个选择,并且会得出相同的结论。但是让我们仔细看看它们,看看它们之间的优势:
(B) 您可以将 post 实体(预览和完整表示)合并为您的减速器中的一个实体,但您会跟踪 result
数组(预览和完整表示),您可以在 API 请求后从 normalizr 规范化数据中获得。如果你已经有了 post 的完整表示,那么你以后就可以轻松区分了。您的 sub-state 可能如下所示:
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 + action,每个表示一个,以区分实体。根据 PREVIEW 或 FULL posts API 请求,您将通过一个显式操作为您的 reducer 之一提供服务。您的 sub-state 可能如下所示:
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 实体将以其标题 属性 保存两次:一次 previewPostState
一次 fullPostState
一次。一旦您想更改全局状态中的标题 属性,您将不得不在两个地方进行。这样做会违反 Redux 中的单一事实来源。这就是我选择 (B) 的原因:您的 post 实体只有一个位置,但可以通过结果数组清楚地区分它们的表示。
Normalizr 非常擅长创建结构化的 JSON 实体存储库。
我们有很多案例显示数据列表,例如posts
已标准化。在列出 posts
的地方,API 响应仅限于几个关键字段。
我们也有显示其中一个 posts
的情况,尽管我们现在需要从 API 中获取包含所有字段的完整 JSON 实体。
如何最好地处理这个问题?
A 一个单独的减速器,thunk/saga,选择器和动作?
B 只需将从 API 获取的 post
的扩展版本插入减速器。重用之前的选择器等?
将应用程序的状态想象成一个数据库。我建议你使用这个状态形状:
{
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;
}
}
我同意你的两个选择,并且会得出相同的结论。但是让我们仔细看看它们,看看它们之间的优势:
(B) 您可以将 post 实体(预览和完整表示)合并为您的减速器中的一个实体,但您会跟踪 result
数组(预览和完整表示),您可以在 API 请求后从 normalizr 规范化数据中获得。如果你已经有了 post 的完整表示,那么你以后就可以轻松区分了。您的 sub-state 可能如下所示:
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 + action,每个表示一个,以区分实体。根据 PREVIEW 或 FULL posts API 请求,您将通过一个显式操作为您的 reducer 之一提供服务。您的 sub-state 可能如下所示:
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 实体将以其标题 属性 保存两次:一次 previewPostState
一次 fullPostState
一次。一旦您想更改全局状态中的标题 属性,您将不得不在两个地方进行。这样做会违反 Redux 中的单一事实来源。这就是我选择 (B) 的原因:您的 post 实体只有一个位置,但可以通过结果数组清楚地区分它们的表示。