-->

Why is the requirement to always return new object

2019-08-09 23:14发布

问题:

Redux requires that one always returns new state from reducers. For example, I have the following state:

let initialState = {
   prop: 3,
   internalReferenceProp: {a:3}
}

And the reducer that modifies internalReferenceProp. This reducer can be implemented to change only state object reference or both state and internalProperty:

function(state=initialState, action) {
  // changing only state reference
  let newState = Object.assign({}, state);
  newState.internalReferenceProp.a = 7;
  return newState;

  // changing both state and internalReferenceProp reference
  return Object.assign({}, state, {internalReferenceProp: {a:7}})
}

As I've been told first approach is not correct, so my question is what's the reasoning behind requirement to also change internal references? I understand that I should change state reference because it allows for an easy comparison to detect whether state changed, but why change internal references?

回答1:

The first one is clearly not correct because Object.assign does a shallow copy, not a deep one.

// changing only state reference
let newState = Object.assign({}, state);

newState === state // false
newState.internalReferenceProp === state.internalReferenceProp // true

state.internalReferenceProp.a // 3
newState.internalReferenceProp.a = 7 // 7
state.internalReferenceProp.a // 7

You can see that this way, if we change something in newState it gets changed in state as well. This will make the change undetectable if a component is only interested in internalReferenceProp. This is also called a "side-effect" and is a bad practice.

In short, if your input (state in this case) changes in any way, it's called a side-effect and is wrong in redux.

Here's an example of why this is bad:

let data = getData(); // from redux

return (
  <ChildComponent someProp={data.internalReferenceProp} />
);

If we use the version with side-effects, ChildComponent will never re-render because its props didn't change. oldData.internalReferenceProp === newData.internalReferenceProp.