Let's look at the example you linked:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
Yes, that's a shallow copy of state
, creating a new object with everything from the old but with an updated visibilityFilter
. But if you're consistent about treating objects immutably, then it's fine if the new state and the old state share references to other immutable objects. Later, presumably, if you were to change something else, you'd follow the same pattern. At which point, the shallow copy we made above would continue to use the old one, and your new object would use the new one.
If you apply immutability all the way down, a shallow copy at the level you're modifying and all its parent levels is all you need. In the example above, the modification is at the top level, so it's just the one copy.
But what if it were deeper? Let's say you had this object:
let obj = {
a: {
a1: "a1 initial value",
a2: "a2 initial value"
},
b: {
b1: "b1 initial value",
b2: "b2 initial value"
}
};
If you wanted to update a
, that's just like the state example above; you'd do so with just one shallow copy of obj
:
obj = Object.assign({}, obj, {a: {a1: "new a1", a2: "new a2"}});
or with spread properties (currently at Stage 3, usually enabled in JSX transpiler setups, will probably make ES2018):
obj = {...obj, a: {a1: "new a1", a2: "new a2"}};
But what if you just want to update a1
? To do that, you need a copy of a
and of obj
(because if you don't copy obj
, you're modifying the tree it refers to, violating the principal):
obj = Object.assign({}, obj, {a: Object.assign({}, obj.a, {a1: "updated a1"})});
or with spread properties:
obj = {...obj, a: {...obj.a, a1: "updated a1"}};