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 对象。
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 对象。