问题
I'm trying to work out the ideal way to update several top level fields on my state tree while still maintaining split reducers.
Here's a simple solution that I've come up with.
var state = {
fileOrder: [0],
files: {
0:{
id: 0,
name: 'asdf'
}
}
};
function handleAddFile(state, action) {
return {...state, ...{[action.id]:{id: action.id, name: action.name}}};
};
function addFileOrder(state, action) {
return [...state, action.id];
}
// Adding a file should create a new file, and add its id to the fileOrder array.
function addFile(state, action) {
let id = Math.max.apply(this, Object.keys(state.files)) + 1;
return {
...state,
fileOrder: addFileOrder(state.fileOrder, {id}),
files: handleAddFile(state.files, {id, name: action.name})
};
}
Currently I'm able to dispatch a single action {type: ADD_FILE, fileName: 'x'}
, then addFile
creates an action internally to send to addFileOrder
and addFile
.
I'm curious if it is considered a better approach to do either of the below.
Instead dispatch two actions, one to add a file, then get it's id and dispatch an ADD_TO_FILE_ORDER
action with the id.
OR Fire and action like {type: ADD_FILE, name: 'x', id: 1}
, instead of allowing addFile
to calculate the new id. This would allow me to use combineReducers
and filter on action type.
This example is probably trivial, but my actual state tree is a bit more complicated, with each file being added also needing to be added to other entities.
For some additional context, a more complete state tree would look like this.
{
"fileOrder": [0]
"entities": {
"files": {
0: {
id: 0,
name: 'hand.png'
}
},
"animations": {
0: {
id: 0,
name: "Base",
frames: [0]
}
},
"frames": {
0: {
id: 0,
duration: 500,
fileFrames: [0]
}
},
"fileFrames": {
0: {
id: 0,
file: 0,
top: 0,
left: 0,
visible: true
}
}
}
}
Adding a file would need to:
- Add it to the files hash.
- Add it to the fileOrder array.
- Add a fileFrame referencing the file, for each of the frames.
- Add each new fileFrame to the frame that it was created for.
The last two points make me wonder if I'd be able to use combineReducers at all.
回答1:
I ended up finding a pretty simple solution to this problem.
Both of these blocks from the documentation are functionally the same thing.
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
});
// This is functionally equivalent.
function reducer(state, action) {
return {
a: doSomethingWithA(state.a, action),
b: processB(state.b, action),
c: c(state.c, action)
};
}
I ended up tweaking the second block, and always passing along my global state tree. As long as nothing edits the state along the way, all the reducers work fine.
// Simple change to pass the entire state to each reducer.
// You have to be extra careful to keep state immutable here.
function reducer(state, action) {
return {
a: doSomethingWithA(state.a, action, state),
b: processB(state.b, action, state),
c: c(state.c, action, state)
};
}
回答2:
Building on the author's solution:
I've been having this same issue, where I need (just a little) access outside of my reducer's part of of the state. I think this solution can work in practice if you're diligent about not changing anything other than a single value like a flag, or a counter.
It's impurity could get crazy fast if other developers weren't as reserved with their code. Imagine what would happen if a started changing b and c's part of the state, b changing a and c's part, and so on.
You might consider shrinking the surface area of impurity like this:
function reducer(state, action) {
return {
a: doSomethingWithA(state.a, action, state.globals),
b: processB(state.b, action, state.globals),
c: c(state.c, action, state.globals)
};
}
来源:https://stackoverflow.com/questions/33321397/best-way-to-update-related-state-fields-with-split-reducers