为什么Redux需要reducers是纯函数
本文转载自:众成翻译 译者:mjzhang 链接:http://www.zcfy.cc/article/2515 原文:https://medium.freecodecamp.com/why-redux-needs-reducers-to-be-pure-functions-d438c58ae468#.mvfdm0whk
你可能知道Redux依赖函数式编程中的纯函数。这究竟是什么意思呢?
下面的这张图片是来自Redux示例中的一个Todo应用。它目前有四个Todo任务。它展示了所有已完成和未完成的任务,我们可以看到第四个任务为已完成状态。图片的右侧展示了存储在Redux当中的当前state。这是一个包含了所有详细信息的Javascript对象。
图片的右侧展示了存储在Redux当中的当前state。这是一个包含了所有详细信息的Javascript对象。
这是Redux精妙之处。
图注:左边: Todo app <- -> 右边: Redux stated
现在让我们切换第四个Todo任务的状态使其变为未完成。下图右边是应用中新的Redux的state:
图注:应用发生改变时,Redux 更新state
现在如果你去Reducer当中查看“TOGGLE_TODO”部分,“TOGGLE_TODO”是切换应用中一个Todo项的完成和未完成状态,它的代码如下 (点击查看源码):
当你切换一个Todo项的状态,将发生如下过程:reducer函数接收一个表示旧的state的对象(也就是函数的输入),然后通过复制旧对象的所有属性(像id和text)和用新的属性覆盖旧的属性(completed属性)来创建一个新的对象。
纯函数
从本质上讲,纯函数的定义如下:不修改函数的输入值,依赖于外部状态(比如数据库,DOM和全局变量),同时对于任何相同的输入有着相同的输出结果。
举个例子,下面的add函数不修改变量a或b,同时不依赖外部状态,对于相同的输入始终返回相同的结果。
const add = (a, b) => a + b //pure function
现在我们回过头去看reducer函数,它符合上述纯函数的所有特征,因此我们说reducer是纯函数。
但是为什么reducer必须为纯函数?
让我们来看看如果reducer不是纯函数会发生什么。我们注释掉之前reducer中返回新对象的代码部分,然后我们直接修改state的completed属性。
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state;
}
// return {
// ...state,
// completed: !state.completed
// }
//mutate the state’s completed prop directly
state.completed = !state.completed;//change original object
return state;
default: ...
做上述改变后,我们触发TOGGLE_TODO,会发现没有任何变化发生。
下图是Redux的部分源码。
我们阅读源码可以看到,Redux接收一个给定的state(对象),然后通过循环将state的每一部分传递给每个对应的reducer。如果有发生任何改变,reducer将返回一个新的对象。如果不发生任何变化,reducer将返回旧的state。
Redux只通过比较新旧两个对象的存储位置来比较新旧两个对象是否相同(译者注:也就是Javascript对象浅比较)。如果你在reducer内部直接修改旧的state对象的属性值,那么新的state和旧的state将都指向同一个对象。因此Redux认为没有任何改变,返回的state将为旧的state。
但是,我们仍然有一些关键问题没有解答:
-
为什么Redux这样设计?
-
为什么Redux不在其他地方复制一份旧的state,然后将其和reducers返回的对象进行比较?
-
为什么Redux要将这个负担交给开发者?
答案只有一个:因为比较两个Javascript对象所有的属性是否相同的的唯一方法是对它们进行深比较。
但是深比较在真实的应用当中代价昂贵,因为通常js的对象都很大,同时需要比较的次数很多。
因此一个有效的解决方法是作出一个规定:无论何时发生变化时,开发者都要创建一个新的对象,然后将新对象传递出去。同时,当没有任何变化发生时,开发者发送回旧的对象。也就是说,新的对象代表新的state。
必须注意到你只能使用slice(译者注:此处slice类似数组的slice方法,具体可以使用本文例子中解构赋值等方法进行slice)或者类似的机制去复制旧的值到新的对象里。
现在使用了新的策略之后,你能够比较两个对象通过使用!==
比较两个对象的存储位置而不是比较两个对象的所有属性。同时当两个对象不同的时候,你就能知道新的对象已经改变了旧的state(也就是说,JavaScript对象当中的某些属性的值发生了变化)。这正是Redux所采取的策略。
这就是为什么Redux需要reducers是纯函数的原因!