不可变对象,即一个对象在创建之后它的内容不再被修改。在函数式编程的场景中(比如 Redux)经常被使用。如果需要修改数据,不应该修改原本的对象,而应该拷贝一个新的对象做修改,再替换原来的变量指向的对象。比如下面演示一段修改字段值的代码:
let obj = { a: 1, b: 2 };
let modifiedObj = { ...obj, a: 3};
JS 实现不可变对象有语言内置的方式,也有第三方库。
语言内置的方式
Spread operator 或 Object.assign
可以实现。一般用 spread operator,因为更简单好理解。Object.assign
的方式不在这里描述。
对于数组:
const numbers = [1, 2, 3];
// Adding
const index = numbers.indexOf(2);
const added = [...numbers.slice(0, index), 4, ...numbers.slice(index)];
// Removing
const removed = numbers.filter(n => n !== 2);
// Updating
const updated = numbers.map(n => (n === 2 ? 20 : n));
console.log(updated);
对于 Object
:
const address = {
country: "USA",
city: "San Francisco"
};
// Add new field
const added = {
...address,
zipCode: 94102
};
// Removing (country field is removed)
const { country, ...removed } = address;
console.log(removed);
// Updating
const updated = {
...address,
city: "New York"
};
console.log(person);
第三方库
Immer
它的原理是,提供一个代理了原始数据 baseState
的草稿对象 draftState
,你在这个草稿上做的改动会被 Immer 纪录,用来生成一个新的对象:
import produce from "immer"
const baseState = [
{
todo: "Learn typescript",
done: true
},
{
todo: "Try immer",
done: false
}
]
const nextState = produce(baseState, draftState => {
draftState.push({todo: "Tweet about it"})
draftState[1].done = true
})
Redux Toolkit 底层使用了 Immer。
Immutable.js
Immutable.js 是 Facebook 维护的。它自定义了一个 Map 对象,在这个 Map 上做 set()
操作修改字段:
import { Map } from "immutable";
let book = Map({ title: "Harry Potter" });
function publish(book) {
return book.set("isPublished", true);
}
book = publish(book);
console.log(book.toJS());
Immer v.s. Immutable.js
Immer 在实现上更优雅,你操作的是就是一个 JavaScript object,而 Immutable.js 需要使用它自定义的 Map 结构。这使得后者在与第三方库整合上势必会更麻烦。
应该使用 Immer。