Redux-不可变更新
在Redux中, reducer函数遵循不可变更新模式, 而对象和数组在JavaScript中是通过引用传递(pass by reference), 因此如果直接修改对象和数组, 原对象和数组就会改变, 于是我们需要对对象和数组的拷贝进行修改, 下面整理了一些修改时要避免以及推荐使用的方法.
可以在jsbin中输入代码用以测试效果.
1 |
|
在html模板中引入expect库用以断言, deep-freeze库用以避免可变数据(avoid mutation).
数组
插入元素
❎ push1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const addCounter = (list) => {
list.push(0);
return list;
};
const testAddCounter = () => {
const listBefore = [];
const listAfter = [0];
deepFreeze(listBefore);
expect(
addCounter(listBefore)
).toEqual(listAfter);
};
testAddCounter();
console.log('All tests passed');
上述代码中, 使用push在list中添加元素, 尽管达到要求插入了元素, 但是修改了listBefore, 无法通过deepFreeze那一部分的测试, 这在redux应用中是不被允许的, 我们应该要避免初始state的改变, 有两种方法可以解决这个问题:
- 用concat方法代替push方法, 修改的是原数组的拷贝
- 使用ES6扩展运算符(
...
)
代码如下:
✅ concat或spread operator(扩展运算符...
)1
2
3
4
5
6const addCounter = (list) => {
// 1. concat()
return list.concat([0]);
// 2. spread operator
// return [...list, 0];
};
移除元素
❎ splice1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const removeCounter = (list, index) => {
list.splice(index, 1);
return list;
};
const testRemoveCounter = () => {
const listBefore = [0, 10, 20];
const listAfter = [0, 20];
deepFreeze(listBefore);
expect(
removeCounter(listBefore, 1)
).toEqual(listAfter);
};
testRemoveCounter();
console.log('All tests passed');
使用splice方法直接改变listBefore, 不可取, 改进版本:
✅ slice和...
1
2
3
4
5
6
7const removeCounter = (list, index) => {
return [
...list.slice(0, index),
...list.slice(index + 1)
];
// 拷贝index之前和之后的元素并将两者合并
};
使用扩展运算符和slice方法, 拷贝index之前和之后的元素, 并将两者合并后返回, 不改变原有的list, 其中扩展运算符也可以用cancat方法代替实现, 但扩展运算符更简便.
修改数组中某个元素值
❎ 直接修改元素
1 | const incrementCounter = (list, index) => { |
修改某个元素值所使用的方法和移除元素所使用方法类似, 都是用slice和…创建数组的拷贝后再进行相应操作.
✅ 修改拷贝
1 | const incrementCounter = (list, index) => { |
对象
❎1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26const toggleTodo = (todo) => {
todo.completed = !todo.completed;
return todo;
};
const testToggleTodo = () => {
const todoBefore = {
id: 0,
text: 'Learn Redux',
completed: false
};
const todoAfter = {
id: 0,
text: 'Learn Redux',
completed: true
};
deepFreeze(todoBefore);
expect(
toggleTodo(todoBefore)
).toEqual(todoAfter);
};
testToggleTodo();
console.log('All tests passed.');
有一种解决方法是手动对原对象进行拷贝, 然后仅修改需要修改的那一部分属性, 尽管能通过测试, 但是如果之后在对象中添加属性, 有可能会忘记更新拷贝的对象, 所以这种方法不可取.1
2
3
4
5
6
7const toggleTodo = (todo) => {
return {
id: todo.id,
text: todo.text,
completed: !todo.completed
};
};
✅ ES6语法: Object.assign()
1 | const toggleTodo = (todo) => { |
✅ spread operator(...
)
1 | const toggleTodo = (todo) => { |
参考