在 redux reducer 中更新状态的正确方法
Right way to update state in redux reducers
我是 redux 和 es6 语法的新手。我用官方的 redux 教程和这个 example 制作我的应用程序。
下面有 JS 片段。我的观点 - 在 posts reducer 中定义 REQUEST_POST_BODY 和 RECEIVE_POST_BODY 案例。
主要困难 - 在商店中找到并更新正确的对象。
我尝试使用示例中的代码:
return Object.assign({}, state, {
[action.subreddit]: posts(state[action.subreddit], action)
})
但它使用简单的 post 数组。不需要通过id找对post.
这是我的代码:
const initialState = {
items: [{id:3, title: '1984', isFetching:false}, {id:6, title: 'Mouse', isFetching:false}]
}
// Reducer for posts store
export default function posts(state = initialState, action) {
switch (action.type) {
case REQUEST_POST_BODY:
// here I need to set post.isFetching => true
case RECEIVE_POST_BODY:
// here I need to set post.isFetching => false and post.body => action.body
default:
return state;
}
}
function requestPostBody(id) {
return {
type: REQUEST_POST_BODY,
id
};
}
function receivePostBody(id, body_from_server) {
return {
type: RECEIVE_POST_BODY,
id,
body: body_from_server
};
}
dispatch(requestPostBody(3));
dispatch(receivePostBody(3, {id:3, body: 'blablabla'}));
有数组
如果您更愿意坚持使用数组,那么您可以编写一个只处理单个 post
对象的缩减器。
export default function reducePost(post, action) {
if(post.id !== action.id) return post;
switch(action.type) {
case REQUEST_POST_BODY:
return Object.assign({}, post, { isFetching: true });
case RECEIVE_POST_BODY:
return Object.assign({}, post, { isFetching: false, body: action.body });
default:
return post;
}
您的根减速器将变为:
export default function posts(state = initialState, action) {
return state.map(post => reducePost(post, action);
}
我们只是 运行 列表中每个 post 的新减速器,到 return 更新的 post 数组。在这种情况下,唯一 ID 将确保只会更改一项。
有对象
如果每个项目都有一个唯一的 string/number ID,那么您可以翻转数组并改用 object
。
const initialState = {
items: {
3: {id:3, title: '1984', isFetching:false},
6: {id:6, title: 'Mouse', isFetching:false}
};
}
然后你可以简化你的reducer。
switch (action.type) {
case REQUEST_POST_BODY:
let id = action.id;
return Object.assign({}, state, {
[id]: Object.assign({}, state[id], { isFetching: true })
});
case RECEIVE_POST_BODY:
let id = action.id;
return Object.assign({}, state, {
[id]: Object.assign({}, state[id], {
isFetching: false,
body: action.body
})
});
default:
return state;
}
如果您也乐于尝试一些 ES7 语法,您可以使用 Babel 启用对象扩展运算符并重写对 Object.assign
.
的调用
switch (action.type) {
case REQUEST_POST_BODY:
let id = action.id;
return {
...state,
[id]: {...state[id], isFetching: true }
};
case RECEIVE_POST_BODY:
let id = action.id;
return {
...state,
[id]: {
...state[id],
isFetching: false,
body: action.body
}
};
default:
return state;
}
如果您不太热衷于使用扩展语法,那么仍然可以使 Object.assign
更容易接受一些。
function $set(...objects) {
return Object.assign({}, ...objects);
}
case RECEIVE_POST_BODY:
let id = action.id;
return $set(state, {
[id]: $set(state[id], {
isFetching: false,
body: action.body
})
});
如果我没理解错的话,您在获取所需的 post 时遇到了问题。
首先,让你的 reducer 也更新数组和其中的对象,使得它难以阅读和维护。我建议您观看这个 short video 解释关于 reducer 与数组的组合。
您可以使用此处描述的技术来简化代码。
在您的情况下,您将使用 posts
减速器和 post
减速器,而 posts
减速器调用 post
减速器。
至于找到合适的工作对象,Dan Prince 的建议使它变得更容易。
使用对象映射而不是数组会使您更容易。 Dan 的回答中的相关代码片段:
const initialState = {
items: {
3: {id:3, title: '1984', isFetching:false},
6: {id:6, title: 'Mouse', isFetching:false}
];
}
我几乎通过使用 Object.assign
实现了 Object reducer,这很有效,但是随着我们项目的增长和我们添加了一堆依赖组件,它变得非常低效并且渲染非常慢。
如果我知道 immer 我会从一开始就使用它。
本质上,您按如下方式使用 immer,示例中有一个 layers
对象,如下所示:
const initialState = {
layers: {
'layer1': { selected: false },
'layer2': { selected: true }
}
}
Reducers.js(摘录)
import produce from 'immer'
import has from 'lodash/has'
export const layers = (state = null, action) => {
switch (action.type) {
case ADD_LAYER:
// Using `immer.produce` only for consistency
// clearly the whole object changes in this case.
return produce(state, layers => {
// Take a copy of the prebuilt layer
var layer = Object.assign({}, action.layer)
// Add some defaults
if (!has(layer, 'selected')) {
layer.selected = false
}
layers[action.id] = layer
})
case SELECT_LAYER:
return produce(state, layers => {
layers[action.id].selected = true
})
case UNSELECT_LAYER:
return produce(state, layers => {
layers[action.id].selected = false
})
default:
return state
}
}
Actions.js(摘录)
export const addLayer = id => ({
type: ADD_LAYER,
id
})
export const selectLayer = id => ({
type: SELECT_LAYER,
id
})
export const unSelectLayer = id => ({
type: UNSELECT_LAYER,
id
})
参考文献:
https://github.com/immerjs/immer
https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns
我是 redux 和 es6 语法的新手。我用官方的 redux 教程和这个 example 制作我的应用程序。
下面有 JS 片段。我的观点 - 在 posts reducer 中定义 REQUEST_POST_BODY 和 RECEIVE_POST_BODY 案例。 主要困难 - 在商店中找到并更新正确的对象。
我尝试使用示例中的代码:
return Object.assign({}, state, {
[action.subreddit]: posts(state[action.subreddit], action)
})
但它使用简单的 post 数组。不需要通过id找对post.
这是我的代码:
const initialState = {
items: [{id:3, title: '1984', isFetching:false}, {id:6, title: 'Mouse', isFetching:false}]
}
// Reducer for posts store
export default function posts(state = initialState, action) {
switch (action.type) {
case REQUEST_POST_BODY:
// here I need to set post.isFetching => true
case RECEIVE_POST_BODY:
// here I need to set post.isFetching => false and post.body => action.body
default:
return state;
}
}
function requestPostBody(id) {
return {
type: REQUEST_POST_BODY,
id
};
}
function receivePostBody(id, body_from_server) {
return {
type: RECEIVE_POST_BODY,
id,
body: body_from_server
};
}
dispatch(requestPostBody(3));
dispatch(receivePostBody(3, {id:3, body: 'blablabla'}));
有数组
如果您更愿意坚持使用数组,那么您可以编写一个只处理单个 post
对象的缩减器。
export default function reducePost(post, action) {
if(post.id !== action.id) return post;
switch(action.type) {
case REQUEST_POST_BODY:
return Object.assign({}, post, { isFetching: true });
case RECEIVE_POST_BODY:
return Object.assign({}, post, { isFetching: false, body: action.body });
default:
return post;
}
您的根减速器将变为:
export default function posts(state = initialState, action) {
return state.map(post => reducePost(post, action);
}
我们只是 运行 列表中每个 post 的新减速器,到 return 更新的 post 数组。在这种情况下,唯一 ID 将确保只会更改一项。
有对象
如果每个项目都有一个唯一的 string/number ID,那么您可以翻转数组并改用 object
。
const initialState = {
items: {
3: {id:3, title: '1984', isFetching:false},
6: {id:6, title: 'Mouse', isFetching:false}
};
}
然后你可以简化你的reducer。
switch (action.type) {
case REQUEST_POST_BODY:
let id = action.id;
return Object.assign({}, state, {
[id]: Object.assign({}, state[id], { isFetching: true })
});
case RECEIVE_POST_BODY:
let id = action.id;
return Object.assign({}, state, {
[id]: Object.assign({}, state[id], {
isFetching: false,
body: action.body
})
});
default:
return state;
}
如果您也乐于尝试一些 ES7 语法,您可以使用 Babel 启用对象扩展运算符并重写对 Object.assign
.
switch (action.type) {
case REQUEST_POST_BODY:
let id = action.id;
return {
...state,
[id]: {...state[id], isFetching: true }
};
case RECEIVE_POST_BODY:
let id = action.id;
return {
...state,
[id]: {
...state[id],
isFetching: false,
body: action.body
}
};
default:
return state;
}
如果您不太热衷于使用扩展语法,那么仍然可以使 Object.assign
更容易接受一些。
function $set(...objects) {
return Object.assign({}, ...objects);
}
case RECEIVE_POST_BODY:
let id = action.id;
return $set(state, {
[id]: $set(state[id], {
isFetching: false,
body: action.body
})
});
如果我没理解错的话,您在获取所需的 post 时遇到了问题。
首先,让你的 reducer 也更新数组和其中的对象,使得它难以阅读和维护。我建议您观看这个 short video 解释关于 reducer 与数组的组合。 您可以使用此处描述的技术来简化代码。
在您的情况下,您将使用 posts
减速器和 post
减速器,而 posts
减速器调用 post
减速器。
至于找到合适的工作对象,Dan Prince 的建议使它变得更容易。 使用对象映射而不是数组会使您更容易。 Dan 的回答中的相关代码片段:
const initialState = {
items: {
3: {id:3, title: '1984', isFetching:false},
6: {id:6, title: 'Mouse', isFetching:false}
];
}
我几乎通过使用 Object.assign
实现了 Object reducer,这很有效,但是随着我们项目的增长和我们添加了一堆依赖组件,它变得非常低效并且渲染非常慢。
如果我知道 immer 我会从一开始就使用它。
本质上,您按如下方式使用 immer,示例中有一个 layers
对象,如下所示:
const initialState = {
layers: {
'layer1': { selected: false },
'layer2': { selected: true }
}
}
Reducers.js(摘录)
import produce from 'immer'
import has from 'lodash/has'
export const layers = (state = null, action) => {
switch (action.type) {
case ADD_LAYER:
// Using `immer.produce` only for consistency
// clearly the whole object changes in this case.
return produce(state, layers => {
// Take a copy of the prebuilt layer
var layer = Object.assign({}, action.layer)
// Add some defaults
if (!has(layer, 'selected')) {
layer.selected = false
}
layers[action.id] = layer
})
case SELECT_LAYER:
return produce(state, layers => {
layers[action.id].selected = true
})
case UNSELECT_LAYER:
return produce(state, layers => {
layers[action.id].selected = false
})
default:
return state
}
}
Actions.js(摘录)
export const addLayer = id => ({
type: ADD_LAYER,
id
})
export const selectLayer = id => ({
type: SELECT_LAYER,
id
})
export const unSelectLayer = id => ({
type: UNSELECT_LAYER,
id
})
参考文献:
https://github.com/immerjs/immer
https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns