Vid*_*tiz 2 javascript immutability redux
由于我的团队使用预先存在的代码作为新更改的模板,因此我尝试在我的应用程序中对 reducer 进行更防御性的编码。出于这个原因,我试图涵盖与不变性的所有潜在偏差。
假设您有一个如下所示的初始状态:
const initialState = {
section1: { a: 1, b: 2, c: 3 },
section2: 'Some string',
};
Run Code Online (Sandbox Code Playgroud)
一个减速器处理这样的动作:
export default function(state = initialState, action) {
switch(action.type) {
case SOME_ACTION:
return { ...state, section1: action.payload.abc };
}
return state;
}
Run Code Online (Sandbox Code Playgroud)
然后你可以有一个执行以下操作的调度程序:
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;
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,虽然reducer通过创建旧状态的浅拷贝并仅更改更改的位来遵循不变性模式,但调度程序仍然可以访问action.payload.abc并可以对其进行变异。
也许 redux 已经创建了整个操作的深层副本,但还没有找到任何提到这一点的来源。我想知道是否有一种方法可以简单地解决这个问题。
应该注意的是,在您的示例中,如果您只是根据对象执行适当的级别复制,则突变不会引起任何问题。
为了abc寻找像{ a: 1, b: 2, c: 3 }你可以做一个浅拷贝而对于嵌套的对象{ a: { name: 1 } },你不得不深复制它,但你仍然可以做到这一点明确的,没有任何图书馆或任何东西。
{
...state,
a: {
...action.a
}
}
Run Code Online (Sandbox Code Playgroud)
或者,您可以使用eslint-plugin-immutable来防止突变,这将迫使程序员不要编写此类代码。
正如您在上面对no-mutation规则的 ESLint 插件的描述中所见:
此规则与使用 Object.freeze() 来防止 Redux reducer 中的突变一样有效。但是,此规则没有运行时成本。对象变异的一个很好的替代方法是使用 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;
}
Run Code Online (Sandbox Code Playgroud)
下面是一个例子:
{
...state,
a: {
...action.a
}
}
Run Code Online (Sandbox Code Playgroud)
Object.freeze是一个浅层操作,因此您必须手动冻结对象的其余部分或使用像deep-freeze这样的库。
上述方法将保护突变的责任推给了您。
这种方法的一个缺点是,它通过明确的突变保护增加了程序员必要的认知努力,因此更容易出现错误(尤其是在大型代码库中)。
深度冻结时也可能会有一些性能开销,特别是如果使用一个库来测试/处理您的特定应用程序可能从未引入的各种边缘情况。
一种更具可扩展性的方法是将不变性模式嵌入到您的代码逻辑中,这将自然地将编码模式推向不可变操作。
一种方法是使用Immutable 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;
}
Run Code Online (Sandbox Code Playgroud)
另一种方法是使用immerwhich 在幕后隐藏不变性,并通过遵循写时复制原则为您提供可变的 api 。
import produce from 'immer'
export default = (state = initialState, action) =>
produce(state, draft => {
switch (action.type) {
case SOME_ACTION:
draft.section1 = action.section1;
})
}
Run Code Online (Sandbox Code Playgroud)
如果您正在转换可能有大量执行突变的代码的现有应用程序,则此库可能对您有用。
不变性库的缺点是它增加了不熟悉该库的人进入您的代码库的障碍,因为现在每个人都必须学习它。
话虽如此,一致的编码模式通过明确限制代码的构建方式来减少认知工作(每个人都使用相同的模式)并减少代码混乱因素(防止人们一直发明自己的模式)。这自然会导致更少的错误和更快的开发。
| 归档时间: |
|
| 查看次数: |
1353 次 |
| 最近记录: |