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


Redux的一个核心概念是,状态 一成不变。但是,我看到了许多例子,包括 Redux文档  使用javascript Object.assign。然后,我看到了这个警告 MDN

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

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


3332
2017-09-03 16:31


起源



答案:


让我们看看你链接的例子:

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"}});

或者 传播属性 (目前处于第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"}};

8
2017-09-03 16:44





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

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


2
2017-09-03 17:05





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

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

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

为何避免深度复制?

  • 更好的浅拷贝性能
  • 浅拷贝的内存占用量更小

不需要深层复制的无需代码的示例(在幕后,它使用 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

0
2017-09-03 16:35



这并不能解决为什么不需要深度克隆的问题。 - Lazar Ljubenović
为什么要这么做? - ideaboxer
因为这是个问题。 - Lazar Ljubenović
这个问题意味着不变性需要深度复制,我试图用我的答案来反驳。 - ideaboxer


答案:


让我们看看你链接的例子:

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"}});

或者 传播属性 (目前处于第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"}};

8
2017-09-03 16:44





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

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


2
2017-09-03 17:05





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

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

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

为何避免深度复制?

  • 更好的浅拷贝性能
  • 浅拷贝的内存占用量更小

不需要深层复制的无需代码的示例(在幕后,它使用 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

0
2017-09-03 16:35



这并不能解决为什么不需要深度克隆的问题。 - Lazar Ljubenović
为什么要这么做? - ideaboxer
因为这是个问题。 - Lazar Ljubenović
这个问题意味着不变性需要深度复制,我试图用我的答案来反驳。 - ideaboxer