每个动作都会创建一个全新的状态对象
是的,但不会重新创建由combineReducers 分配的状态切片。这有点类似于这样做:
const person = { name: 'Rob' };
const prevState = { person };
const nextState = { person };
我创建了一个新的状态对象 (nextState),但它的 person 键仍设置为与 prevState 的 person 键相同的对象。内存中只有一个字符串 'Rob' 的实例。
问题是当我改变 person 对象时,我将其更改为多个状态:
const person = { name: 'Rob' };
const prevState = { person };
person.name = 'Dan'; // mutation
const nextState = { person };
console.log(prevState.person.name); // 'Dan'
回到 Redux,一旦所有 reducer 第一次被调用,它们就会初始化它们的应用程序状态切片,而您的应用程序的整个整体状态基本上是这样的:
{
firstReducer: Immutable.Map({greeting : 'Hey!'}),
secondReducer: Immutable.Map({foo : 'bar'}),
}
请注意,这是一个普通对象。它具有保存不可变对象的属性。
当一个动作被调度并通过每个reducer时,reducer只是再次返回现有的Immutable对象,它不会创建一个新对象。然后将新状态设置为具有属性firstReducer 的对象,该对象简单地指向先前状态指向的同一个不可变对象。
现在,如果我们不对 firstReducer 使用 Immutable 会怎样:
const firstReducer = (state = {greeting : 'Hey!'}) => {
return state
}
同样的想法,当第一次调用 reducer 时用作状态默认值的对象只是从前一个状态传递到下一个状态。内存中只有一个对象具有键 greeting 和值 Hey!。有很多状态对象,但它们只是有一个键 firstReducer 指向同一个对象。
这就是为什么我们需要确保我们不会意外改变它,而是在我们对其进行任何更改时替换它。当然,您可以在不使用 Immutable 的情况下通过小心谨慎来完成此操作,但使用 Immutable 会使其更加万无一失。如果没有 Immutable,可能会搞砸并这样做:
const firstReducer = (state = {greeting : 'Hey!'}, action) => {
switch (action.type) {
case 'CAPITALIZE_GREETING': {
const capitalized = state.greeting.toUpperCase();
state.greeting = capitalized; // BAD!!!
return state;
}
default: {
return state;
}
}
}
正确的方法是创建一个新的状态切片:
const firstReducer = (state = {greeting : 'Hey!'}, action) => {
switch (action.type) {
case 'CAPITALIZE_GREETING': {
const capitalized = state.greeting.toUpperCase();
const nextState = Object.assign({}, state, {
greeting: capitalized,
};
return nextState;
}
default: {
return state;
}
}
}
Immutable 给我们的另一个好处是,如果我们的 reducer 的状态切片碰巧有很多其他数据,除了 greeting,Immutable 可以在后台进行一些优化,这样它就不必重新创建如果我们所做的只是更改单个值,则每条数据都可以同时确保不变性。这很有用,因为它可以帮助减少每次分派操作时放入内存中的内容量。