# immutability-helper
immutability意为:不变、永恒
TIP
在 JavaScript 中,简单类型的数据被设计为不可变,但是复杂类型如数组、对象则是可变的。
也就是说我们无法保证在字符串内存地址不变的情况下改变字符串,但却可以保证在数组内存地址不变的情况下增加或删除数组的某一个元素。
所以这也是 state更新了,但没有重新渲染的原因。
举个例子:
现在有数组 hobbies ,将 hobbies 赋值给 hobbies2,再将 hobbies2 push 进一个新的数据,这时会发生什么呢?
const hobbies = ['qq', 'wx']; // undefined
const hobbies2 = hobbies; // undefined
hobbies2.push('dd'); // 3
hobbies === hobbies2; // true
1
2
3
4
2
3
4
可以看出,hobbies 和 hobbies2 拥有同一个内存地址,也就是说 hobbies 和 hobbies2 实际上是一个变量
在实际项目中,通常会有层次很深且复杂的数据要进行处理,如果有一个很里层的数据要进行处理,这时就很头疼。我们常用有以下几种做法
- 直接修改数据,上一个副本会被覆盖,无法确定哪些数据被更改。
myData.x.y.z = 7;
myData.a.b.push(9);
1
2
2
- 使用深拷贝,新建 myData 的副本,仅更改需要更改的部分。
const newData = deepCopy(myData);
newData.x.y.z = 7;
newData.a.b.push(9);
1
2
3
2
3
注意:深拷贝是很昂贵的,有的时候甚至是不可能的。
- 仅复制需要更改的对象和重新使用未更改的对象
const newData = Object.assign({}, myData, {
x: Object.assign({}, myData.x, {
y: Object.assign({}, myData.x.y, {z: 7}),
}),
a: Object.assign({}, myData.a, {b: myData.a.b.concat(9)})
});
1
2
3
4
5
6
2
3
4
5
6
在现在的 JavaScript 中,这种写法有些麻烦,甚至可能会产生 bug 。
# immutability-helper 的 update
import update from 'immutability-helper';
const newData = update(myData, {
x: {y: {z: {$set: 7}}},
a: {b: {$push: [9]}}
});
1
2
3
4
5
6
2
3
4
5
6
# Commands
以
$开头的称作 commands。
# {$push: array}
向数组末尾添加一个或多个元素
const initialArray = [1, 2, 3]; // => [1, 2, 3]
const newArray = update(initialArray, {$push: [4]}); // => [1, 2, 3, 4]
// initialArray is still [1, 2, 3].
1
2
3
2
3
# {$unshift: array}
在数组开头添加一或多个元素
const initialArray = [2, 3, 4]; // => [2, 3, 4]
const newArray = update(initialArray, {$unshift: [1]}); // => [1, 2, 3, 4]
// This accesses collection’s index 2, key a, and does a splice of one item starting from index 1 (to remove 17) while inserting 13 and 14.
1
2
3
2
3
# {$splice: array of arrays}
从数组中添加/删除元素
const collection = [1, 2, 12, 17, 15];
// => [1, 2, 12, 17, 15]
const newCollection = update(collection, {$splice: [[1, 1, 13, 14]]});
// => [1, 13, 14, 12, 17, 15]
const collection1 = [1, 2, {a: [12, 17, 15]}];
// => [1, 2, {a: [12, 17, 15]}]
const newCollection1 = update(collection, {2: {a: {$splice: [[1, 1, 13, 14]]}}});
// => [1, 2, {a: [12, 13, 14, 15]}]
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# {$set: any}
给对象某个元素赋值
const obj = {a: 5, b: 3};
const newObj2 = update(obj, {b: {$set: obj.b * 2}});
// => {a: 5, b: 6}
// 计算属性名称用 [] 包裹
const collection = {children: ['zero', 'one', 'two']};
const index = 1;
const newCollection = update(collection, {children: {[index]: {$set: 1}}});
// => {children: ['zero', 1, 'two']}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# {$toggle: array of strings}
切换目标对象的布尔字段列表
const origin = { isCat: [true, false, false] };
// => { isCat: [true, false, false] }
const result = update(origin, {isCat: {$toggle: [1]}});
// => { isCat: [true, true, false] }
1
2
3
4
2
3
4
# {$unset: array of strings}
从目标对象中删除数组中的键列表
const collection = [1, 2, 3, 4];
// => [1, 2, 3, 4]
const result = update(collection, {$unset: [1]});
// => [1, empty, 3, 4]
1
2
3
4
2
3
4
# {$merge: object}
合并对象
const obj = {a: 5, b: 3};
const newObj = update(obj, {$merge: {b: 6, c: 7}}); // => {a: 5, b: 6, c: 7}
1
2
2
# {$apply: function}
通过函数将一个值转为另外一个值
const obj = {a: 5, b: 3};
const newObj = update(obj, {b: {$apply: function(x) {return x * 2;}}});
// => {a: 5, b: 6}
1
2
3
2
3
# {$add: array of objects}
为 Map 或者 Set 添加值
const map = new Map([[1, 2], [3, 4]]);
// => Map(2) {1 => 2, 3 => 4}
const result = update(map, {$add: [['foo', 'bar'], ['baz', 'boo']]});
// => Map(4) {1 => 2, 3 => 4, "foo" => "bar", "baz" => "boo"}
1
2
3
4
2
3
4
# {$remove: array of strings}
从 Map 或者 Set 移除值
const map = new Map([[1, 2], [3, 4]]);
// => Map(2) {1 => 2, 3 => 4}
const result = update(map, {$remove: [1]});
// => Map(1) {3 => 4}
1
2
3
4
2
3
4
用了 immutability-helper 以后少写了很多不必要的代码,并且在处理复杂对象的时候要比用原生 API 修改,或者深拷贝一个新的对象优雅很多。immutability-helper 实现的功能还不仅仅只是这些,有兴趣可以自行研究一下源码。它也是一个被antd推荐使用的轮子。
# 如果需要设置深层嵌套的内容,可以参考如下写法
const initial = {}
const content = {
foo: [
{
bar: ['x', 'y', 'z']
},
],
};
const result = update(initial, {
foo: foo =>
update(foo || [], {
0: fooZero =>
update(fooZero || {}, {
bar: bar => update(bar || [], { $push: ["x", "y", "z"] })
})
})
});
console.log(JSON.stringify(result) === JSON.stringify(content)) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 你也可以使用 extend 功能添加你自己的命令
import update, { extend } from 'immutability-helper';
extend('$addtax', function(tax, original) {
return original + (tax * original);
});
const state = { price: 123 };
const withTax = update(state, {
price: {$addtax: 0.8},
});
assert(JSON.stringify(withTax) === JSON.stringify({ price: 221.4 }));
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10