繁体   English   中英

如何防御性地防止Redux状态突变?

[英]How to defensively prevent mutation of the Redux state?

由于我的团队使用预先存在的代码作为新更改的模板,因此我试图为我的应用程序中的reducer编写一些防御性代码。 因此,我试图涵盖与不变性有关的所有潜在偏差。

假设您的初始状态如下所示:

const initialState = {
  section1: { a: 1, b: 2, c: 3 },
  section2: 'Some string',
};

以及一个reducer处理这样的动作:

export default function(state = initialState, action) {
  switch(action.type) {
    case SOME_ACTION:
      return { ...state, section1: action.payload.abc };
  }
  return state;
}

然后,您可以有一个执行以下任务的调度程序:

function foo(dispatch) {
  const abc = { a: 0, b: 0, c: 0 };
  dispatch({ type: SOME_ACTION, payload: { abc } });

  // The following changes the state without dispatching a new action
  // and does so by breaking the immutability of the state too.
  abc.c = 5;
}

在这种情况下,当reducer通过创建旧状态的浅表副本并仅更改已更改的位来遵循不变性模式时,调度程序仍然可以访问action.payload.abc并可以对其进行更改。

也许redux已经创建了整个动作的深层副本,但是还没有找到任何提及这一点的资料。 我想知道是否有一种方法可以简单地解决此问题。

应当注意,在您的示例中,如果仅基于对象执行适当的级别复制,则该突变不会引起任何问题。

对于看起来像{ a: 1, b: 2, c: 3 } abc { a: 1, b: 2, c: 3 }您可以进行浅表复制,而对于嵌套对象{ a: { name: 1 } } ,则必须进行深复制,但仍然可以明确地执行此操作,而无需任何库或任何东西。

{
  ...state,
  a: {
    ...action.a
  }
}

另外,您可以使用eslint-plugin-immutable防止突变,这会迫使程序员不要编写此类代码。

正如您在上面的ESLint插件的说明中所看到的,是no-mutation规则

该规则与使用Object.freeze()防止Redux还原器中的突变一样有效。 但是,此规则没有运行时成本。 一个很好的替代对象变异的方法是使用ES2016中提供的对象传播语法。


主动防止突变的另一种相对简单的方法(不使用某些不变性库)是在每次更新时冻结状态。

export default function(state = initialState, action) {
  switch(action.type) {
    case SOME_ACTION:
      return Object.freeze({
        ...state,
        section1: Object.freeze(action.payload.abc)
      });
  }
  return state;
}

这是一个例子:

 const object = { a: 1, b: 2, c : 3 }; const immutable = Object.freeze(object); object.a = 5; console.log(immutable.a); // 1 

Object.freeze是一个浅层操作,因此您必须手动冻结其余对象或使用诸如deep-freeze之类的库。

上面的方法将保护突变的责任带给了您。

这种方法的一个缺点是,它通过明确显示突变保护来增加程序员的必要认知工作量,因此更容易出现错误(尤其是在大型代码库中)。

深度冻结时可能还会有一些性能开销,特别是如果使用用于测试/处理您的特定应用程序可能永远不会引入的各种极端情况的库,则尤其如此。


更具可扩展性的方法是将不变性模式嵌入到代码逻辑中,这将自然地将编码模式推向不变性操作。

一种方法是使用不可变JS ,其中数据结构本身的构建方式是,对它们的操作始终会创建一个新实例,并且永不变异。

import { Map } from 'immutable';

export default function(state = Map(), action) {
  switch(action.type) {
    case SOME_ACTION:
      return state.merge({ section1: action.payload.abc });
      // this creates a new immutable state by adding the abc object into it
      // can also use mergeDeep if abc is nested
  }
  return state;
}

另一种方法是使用immer ,该immer器将不可变性隐藏在幕后,并遵循写时复制原理为您提供可变的api。

import produce from 'immer'

export default = (state = initialState, action) =>
  produce(state, draft => {
    switch (action.type) {
      case SOME_ACTION:
        draft.section1 = action.section1;
    })
}

如果您要转换一个现有的应用程序,该应用程序可能包含许多执行变异的代码,那么该库可能对您来说效果很好。

不变性库的缺点在于,它增加了不熟悉该库的人进入代码库的障碍,因为现在每个人都必须学习它。

就是说,一致的编码模式通过明确限制代码的构建方式,减少了认知工作量(每个人都使用相同的模式),并减少了代码混乱因素(防止人们始终发明自己的模式)。 这自然会减少错误并加快开发速度。

我是Redux的维护者。

Redux本身并没有采取任何措施来防止状态突变 部分原因是因为Redux不知道或不在乎实际状态。 它可以是一个数字,普通的JS对象和数组,Immutable.js映射和列表或其他内容。

话虽如此, 现有一些开发附件可以捕获偶然的突变

我将特别建议您尝试使用我们的新redux-starter-kit软件包 现在,当您使用configureStore()函数时,默认情况下它将在开发模式下将redux-immutable-state-invariant中间件添加到您的商店中,并且还检查是否意外添加了不可序列化的值。 另外,它的createReducer()实用程序使您可以定义化简器,以通过“改变”状态来简化不可变的更新逻辑,但是更新实际上是不可变的。

我相信在reducer中,如果您从action.payload.abc创建一个新对象,则可以确保原始对象的任何更改都不会影响redux存储。

case SOME_ACTION:
      return { ...state, section1: {...action.payload.abc}};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM