Javascript 在 redux reducer 中更新状态的正确方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/36031590/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Right way to update state in redux reducers
提问by Max Vorozhcov
I'm a newbie in redux and es6 syntax. I make my app with official redux tutorial, and with this example.
我是 redux 和 es6 语法的新手。我使用官方 redux 教程制作我的应用程序,并使用此示例.
There is JS snippet below. My point - to define REQUEST_POST_BODY and RECEIVE_POST_BODY cases in posts reducer. Main difficult - to find and update right object in store.
下面有JS片段。我的观点 - 在帖子减速器中定义 REQUEST_POST_BODY 和 RECEIVE_POST_BODY 案例。主要困难 - 在商店中找到并更新正确的对象。
I try to use code from example:
我尝试使用示例中的代码:
return Object.assign({}, state, {
[action.subreddit]: posts(state[action.subreddit], action)
})
But it used simple array of posts. It's not needed to find right post by id.
但它使用了简单的帖子数组。不需要通过 id 找到正确的帖子。
Here my code:
这是我的代码:
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'}));
回答by Dan Prince
With Arrays
使用数组
If you'd prefer to stick with arrays, then you can write a reducer that just tackles single post
objects.
如果您更喜欢坚持使用数组,那么您可以编写一个只处理单个post
对象的 reducer 。
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;
}
Your root reducer would become:
你的根减速器将变成:
export default function posts(state = initialState, action) {
return state.map(post => reducePost(post, action);
}
We're just running our new reducer over each post in the list, to return an updated array of posts. In this case, the unique id will ensure that only one item will be changed.
我们只是在列表中的每个帖子上运行我们的新减速器,以返回更新的帖子数组。在这种情况下,唯一 id 将确保仅更改一项。
With Objects
有对象
If each item has a unique string/number id, then you can flip your array around and use an object
instead.
如果每个项目都有一个唯一的字符串/数字 id,那么您可以翻转数组并使用一个object
来代替。
const initialState = {
items: {
3: {id:3, title: '1984', isFetching:false},
6: {id:6, title: 'Mouse', isFetching:false}
};
}
Then you can simplify your 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;
}
If you're happy to experiment with some ES7 syntax too, you can enable the Object spread operator with Babel and rewrite the calls to Object.assign
.
如果您也乐于尝试一些 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;
}
If you're not so keen on using the spread syntax, then it's still possible to make Object.assign
a bit more palatable.
如果您不太热衷于使用扩展语法,那么仍然可以使它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
})
});
回答by Moti Azu
If I understand correctly, you are having trouble getting the specific post you want.
如果我理解正确,您将无法获得所需的特定帖子。
First of all, Having your reducer also update the array and the object in it, makes it hard to read and maintain. I suggest you watch this short videoexplaining about reducer composition with arrays. You can simplify your code by using the technique described there.
首先,让你的 reducer 也更新数组和其中的对象,这使得阅读和维护变得困难。我建议您观看这个简短的视频,其中解释了使用数组的 reducer 组合。您可以使用那里描述的技术来简化您的代码。
In your case, you would a posts
reducer and a post
reducer, while posts
reducer calls the post
reducer.
在您的情况下,您将使用posts
减速器和post
减速器,而posts
减速器调用post
减速器。
As for finding the right object to work on, Dan Prince's suggestion makes it easier. Having an object map instead of an array would make it easier for you. Relevant code snippet from Dan's answer:
至于找到合适的工作对象,丹·普林斯的建议让事情变得更容易。拥有一个对象映射而不是一个数组会让你更容易。来自丹回答的相关代码片段:
const initialState = {
items: {
3: {id:3, title: '1984', isFetching:false},
6: {id:6, title: 'Mouse', isFetching:false}
];
}
回答by Simon Hutchison
I pretty much implemented Object reducers by using Object.assign
, which works, but as our project has grown and we have added a bunch of dependent components it has become very inefficient and renders are very slow.
我几乎通过 using 实现了 Object reducers Object.assign
,这很有效,但是随着我们的项目不断发展并且我们添加了一堆依赖组件,它变得非常低效并且渲染非常缓慢。
If I'd know about immerI would have used that from the start.
如果我知道immer我会从一开始就使用它。
Essentially you use immer as follows, where the example has a layers
object that looks like this:
本质上,您按如下方式使用 immer,其中示例有一个如下所示的layers
对象:
const initialState = {
layers: {
'layer1': { selected: false },
'layer2': { selected: true }
}
}
Reducers.js (extract)
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 (extract)
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
})
References:
参考:
https://github.com/immerjs/immer
https://github.com/immerjs/immer
https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns
https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns