Redux:为什么使用Object.assign如果不执行深度克隆?

时间:2017-09-03 16:31:34

标签: javascript redux cloning

Redux中的一个核心概念是,状态为immutable。但是,我看到了许多示例,包括Redux docs使用javascript Object.assign。然后,我在MDN中看到了这个警告:

  

对于深度克隆,我们需要使用其他替代方法,因为   Object.assign()复制属性值。如果源值是a   引用一个对象,它只复制该引用值。

那么,为什么使用Object.assign如果整点是不变的呢?我在这里错过了什么吗?

3 个答案:

答案 0 :(得分:9)

让我们看看您链接的示例:

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    default:
      return state
  }
}

是的,这是state的浅层副本,创建了一个新对象,其中包含旧对象,但更新了visibilityFilter。但是如果你对一致关于不可变地处理对象,那么如果新状态和旧状态共享对其他不可变对象的引用,那就没问题了。后来,大概是,如果你要改变别的东西,你就会遵循相同的模式。在这一点上,我们上面的浅拷贝将继续使用旧的,我的新对象将使用新的。

如果您一直向下应用不变性,那么您正在修改的级别的浅层副本及其所有父级别就是您所需要的。在上面的示例中,修改位于顶层,因此它只是一个副本。

但如果它更深入呢?让我们说你有这个对象:

let obj = {
    a: {
        a1: "a1 initial value",
        a2: "a2 initial value"
    },
    b: {
        b1: "b1 initial value",
        b2: "b2 initial value"
    }
};

如果您想更新a,那就像上面的状态示例一样;您只需使用obj的一个浅表副本即可完成此操作:

obj = Object.assign({}, obj, {a: {a1: "new a1", a2: "new a2"}});

spread properties(目前处于第3阶段,通常在JSX转换程序设置中启用,可能会生成ES2018):

obj = {...obj, a: {a1: "new a1", a2: "new a2"}};

但是如果只是想要更新a1怎么办?要做到这一点,您需要a obj的副本(因为如果您不复制obj,则需要修改它引用的树,违反了委托人):

obj = Object.assign({}, obj, {a: Object.assign({}, obj.a, {a1: "updated a1"})});

或传播属性:

obj = {...obj, a: {...obj.a, a1: "updated a1"}};

答案 1 :(得分:2)

Redux只是一个数据存储。因此,在最纯粹意义上,redux并不真正需要不可变性或深度克隆作为一个概念。

但是,redux需要不变性才能与构建于其上的UI框架(如React)配合良好。

出于这个简单的原因:我的州的哪些部分在上次框架看起来之间发生了变化?

鉴于这个目标,你能看到深度克隆实际上没有帮助吗?如果你看一个已被深度克隆的对象,那么该对象的每个子部分现在在身份方面都是不同的(===)。

作为一个具体示例,如果您运行以下代码:

const bookstore = { name: "Jane's books", numBooks: 42 };
const reduxData = { bookstore, employees: ['Ada', 'Bear'] };

现在让我们说你想改变你在书店的书籍数量。

如果您执行了深层复制,请执行以下操作:

const reduxClone = JSON.parse(JSON.stringify(reduxData));
reduxClone.bookstore.numBooks = 25;

然后你会发现书店和员工现在都不同了:

console.log(reduxData.bookstore === reduxClone.bookstore); // returns false
console.log(reduxData.employees === reduxClone.employees); // returns false, but we haven't changed the employees

这是一个问题,因为它似乎所有已更改。现在React必须重新渲染所有内容,看看是否有任何变化。

正确的解决方案是使用简单的不变性规则。如果更改对象的值,则必须创建该对象的新副本。所以,既然我们想要一个新的numBooks,我们需要创建一个新的书店。由于我们有一个新的书店,我们需要建立一个新的redux商店。

const newBookstore = Object.assign({}, bookstore, {numBooks: 25});
const shallowReduxClone = Object.assign({}, reduxData, {bookstore: newBookstore});

现在,你会看到书店已经改变了(耶!),但员工却没有(双倍!)

console.log(reduxData.bookstore === shallowReduxClone.bookstore); // returns false
console.log(reduxData.employees === shallowReduxClone.employees); // returns true

我希望这个例子有所帮助。 Immutability允许您在进行更改时更改最少量的对象。如果您保证永远不会更改对象,那么您可以在您构建的其他树中重用该对象。在这个例子中,我们能够使用employees对象两次,没有危险,因为我们承诺永远不会改变employees对象。

答案 2 :(得分:1)

不变性意味着:我(作为开发人员)从不为对象属性分配新值;或者因为编译器不允许它,对象被冻结或我只是不这样做。相反,我创建了新对象。

如果你始终记住你不应该改变对象并遵守它,你就永远不会修改现有对象的内容,即使你事先浅层复制它而不是深层复制它(修改现有对象是什么不变的代码允许阻止,因为这使代码的行为更容易预测。)

因此,要创建不受影响的代码,您不需要深度复制。

为什么要避免深度复制?

  • 浅色复制的更好表现
  • 浅拷贝的内存占用量较小

不需要深层复制的无需代码的示例(在幕后,它使用Object.assign):

const state = { firstName: 'please tell me', lastName: 'please tell me' }
const modifiedState = { ...state, firstName: 'Bob' }

当然,如果您使用浅层复制而不是深层复制对象,则可能会出错:

const state = { firstName: 'please tell me', lastName: 'please tell me' }
const modifiedState = state
modifiedState.firstName = 'Bob' // violates immuting, because it changes the original state as well