带有不可变 JS 的 Redux

Redux with Immutable JS

在 Redux 中使用 immutablejs 时,我们将从 combineReducers 返回一个常规的 javascript 对象,这意味着它不会是一个不可变的数据结构,即使其中的所有内容都是。这是否意味着使用 immutablejs 将是徒劳的,因为无论如何都会在每个操作上创建一个全新的状态对象?

示例:

const firstReducer = (state = Immutable.Map({greeting : 'Hey!'})) => {
  return state
}

const secondReducer = (state = Immutable.Map({foo : 'bar'})) => {
  return state
}

const rootReducer = combineReducers({
  firstReducer, secondReducer
})
Redux 附带的

combineReducers() 确实为您提供了一个普通的根对象。

这本身不是问题——您可以将普通对象与不可变对象混合使用,只要您不改变它们即可。所以你可以忍受这个就好了。

您也可以使用 alternative combineReducers() 而 returns 不可变映射。这完全取决于您,除了让您可以在任何地方使用 Immutable 之外,没有任何大的区别。

不要忘记 combineReducers() 实际上是 easy to implement on your own

Doesn't this mean that using immutablejs will be in vain since a whole new state object will be created on every action anyhow?

我不确定你所说的“白费”是什么意思。不可变不会神奇地阻止创建对象。当您 set() 在不可变 Map 中时,您仍然会创建一个新对象,只是(在某些情况下)更有效。无论如何,这对你有两个钥匙的情况没有影响。

所以在这里使用不可变并没有任何缺点,除了你的应用程序在选择数据结构方面稍微不一致。

a whole new state object will be created on every action

是的,但不会重新创建由 combineReducers 分配的该状态的切片。这有点类似于这样做:

const person = { name: 'Rob' };
const prevState = { person };
const nextState = { person };

我创建了一个新的状态对象 (nextState),但它的 person 键仍然设置为与 prevStateperson 键完全相同的对象.内存中只有一个字符串 '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 只是 returns 现有的不可变对象再次返回,它不会创建一个新对象。然后,新状态被设置为一个对象 属性 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 可能会在幕后进行一些优化,这样它就不会如果我们所做的只是更改单个值,则不必重新创建每条数据,但同时仍要确保不变性。这很有用,因为它可以帮助减少每次调度操作时放入内存的内容量。