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

Redux: why using Object.assign if it is not perform deep clone?

Redux 的一个核心概念是,状态是 immutable. However, I saw many examples, including in Redux docs using javascript Object.assign. Then, I saw this warning in MDN:

For deep cloning, we need to use other alternatives because Object.assign() copies property values. If the source value is a reference to an object, it only copies that reference value.

那么,如果重点是不变性,为什么还要使用 Object.assign?我在这里遗漏了什么吗?

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

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

因此,要创建不变代码,您不需要深度复制。

为什么要避免深度复制?

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

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

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

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

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 对象,没有危险,因为我们承诺永远不会改变 employees 对象。