【问题标题】:Redux with Immutable JS带有不可变 JS 的 Redux
【发布时间】:2016-04-12 11:01:52
【问题描述】:

当在 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 immutable.js


    【解决方案1】:

    每个动作都会创建一个全新的状态对象

    是的,但不会重新创建由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只是再次返回现有的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 可以在后台进行一些优化,这样它就不必重新创建如果我们所做的只是更改单个值,则每条数据都可以同时确保不变性。这很有用,因为它可以帮助减少每次分派操作时放入内存中的内容量。

    【讨论】:

      【解决方案2】:

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

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

      您也可以使用返回不可变映射的alternative combineReducers()。这完全取决于您,除了它允许您在任何地方使用 Immutable 之外,没有什么大的区别。

      别忘了combineReducers() 实际上是easy to implement on your own

      这是否意味着使用 immutablejs 将是徒劳的,因为无论如何都会在每个操作上创建一个全新的状态对象?

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

      所以在这里使用 Immutable 并没有任何缺点,除了您的应用在数据结构的选择上稍微不太一致。

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-01-12
      • 1970-01-01
      • 2016-07-06
      • 1970-01-01
      • 1970-01-01
      • 2018-06-20
      • 2018-07-22
      相关资源
      最近更新 更多