为啥 Redux reducer 被称为 reducer?
Posted
技术标签:
【中文标题】为啥 Redux reducer 被称为 reducer?【英文标题】:Why is a Redux reducer called a reducer?为什么 Redux reducer 被称为 reducer? 【发布时间】:2017-03-28 17:23:57 【问题描述】:在学习Redux
的过程中,我遇到了Reducers
。文档指出:
reducer 是一个纯函数,它接受前一个状态和一个动作,并返回下一个状态。 (previousState, action) => newState.它被称为 reducer,因为它是您将传递给 Array.prototype.reduce(reducer, ?initialValue) 的函数类型。
MDN 将reduce
方法描述为:
reduce() 方法对累加器和数组的每个值(从左到右)应用一个函数以将其减少为单个值。
我仍然对为什么 Redux 对 reducer 的定义感到困惑,因为它没有任何意义。其次,MDN 的描述似乎也不正确。 reduce
方法并不总是用于减少到单个值。它可以用来代替map
和filter
,并且在代替链接时实际上更快。
MDN 描述不正确吗?
回到 Redux 的 reducer 定义,它声明:
之所以称为reducer,是因为它是您要传递给Array.prototype.reduce(reducer, ?initialValue)的函数类型
我的印象是 Redux 中的 reducer 负责修改状态。一个reducer示例:
const count = function(state, action)
if(action.type == 'INCREMENT')
return state + 1;
else if(action.type == 'DECREMENT')
return state - 1;
else
return state;
...我不明白这是如何传递给reduce
的函数。这些数据是如何减少到单个值的?如果这是一个您将传递给reduce
的函数,那么state
将是回调,action
将是初始值。
感谢任何清晰的解释。很难概念化。
【问题讨论】:
好问题! 对我来说,来自 WPF 和 C#,动作似乎是“setter”动作,reducers 是“getter”动作。虽然实际上并没有以这种方式在后台运行,因为它返回一个全新的状态对象而不是更改它,但最终结果与它所完成的相似。 作为一名在 Java、.NET、android、Angular、php 和其他堆栈方面拥有超过 15 年经验的开发人员,我可以肯定地说,redux 中的命名对于开发社区来说简直是一种耻辱. Redux 开发人员应该为自己混淆了几代开发人员而感到羞耻。 关于您对 MDN 定义的挑战,您能否提供一个reducer
不返回单个值的示例?请记住,数组仍然是单个值。当然reduce
可以用来代替map
/filter
,但不同之处在于您使用一个值(累加器)而不是单独对每个元素进行操作。
关于第二段到最后一段,整个 reducer 函数作为“reducer”传递 - “初始值”是一个单独的参数。
【参考方案1】:
术语“reduce”实际上是函数式编程中使用的一个函数式术语。在 Haskell、F# 甚至 javascript 之类的语言中,我们定义了一个转换,它将一个集合(任意大小)作为输入并返回单个值作为输出。
所以(不是迂腐,但我发现这对我有帮助)从视觉上考虑它。我们有一个集合:
[][][][][][][][][][]
...我们想将其折叠成单个值:
N
在函数式编程中,我们将使用一个函数来实现这一点,我们可以在集合的每个元素上递归调用该函数。但是如果你这样做,你需要在某个地方跟踪中间值,对吗?非纯实现可能会在函数外部保留某种“累加器”或变量来跟踪状态,如下所示:
var accumulator = 0;
var myArray = [1,2,3,4,5];
myArray.reduce(function (each)
accumulator += 0;
);
return accumulator;
但是,对于纯函数,我们不能这样做 - 因为根据定义,纯函数不能在其函数范围之外产生影响。我们不依赖于封装调用之间“状态”的外部变量,而是简单地在方法中传递状态:
var myArray = [1,2,3,4,5];
return myArray.reduce(function (accumulator, each)
return accumulator + each;
, 0);
在这种情况下,我们将函数称为“reducer”,因为它的方法签名。我们有each
(或current
- 任何名称都可以),代表集合中的一个对象;和state
(或previous
),它被传递给函数的每次迭代,表示我们已经对集合中的先前元素进行的转换的结果。
请注意,您引用的 MDN 文档是正确的; reduce()
函数总是返回单个值。事实上,任何语言中的reduce
方法都是higher-order function,它采用“reducer”(具有上面定义的方法签名的函数)并返回单个值。现在,是的,如果你调用的函数有副作用,你可以用它做其他事情,但你不应该这样做。 (本质上,不要将.reduce()
用作foreach。)即使您使用reduce
调用的方法有副作用,reduce 本身的返回值 将是单个值,而不是收藏。
很酷的是,这种模式不仅仅适用于数组或具体集合,就像您在 React 中看到的那样;这种模式也可以应用于流,因为它们是纯函数。
希望这会有所帮助。对于它的价值,Redux 站点上的定义可以改进(因为 reducer 的概念不仅仅是因为 Javascript 的 Array 原型方法)。你应该提交 PR!
编辑:有一篇关于这个主题的***文章。请注意,reduce 有不同的名称,在函数式语言中,它通常称为折叠。 https://en.wikipedia.org/wiki/Fold_(higher-order_function)#Folds_as_structural_transformations
编辑 (2020-10-03):人们似乎仍然觉得这很有用 - 这很好。随着时间的推移,我意识到“折叠”是一个更好的术语。函数式语言做对了。 “Reducer”这个词其实并不坏,但也不一定是个好词。
【讨论】:
"reduce 本身的返回值将是单个值,而不是集合。" --- 这需要重新措辞。归还收藏并没有错。折叠是一种比任何其他基于集合的操作更强大的操作,因此您可以仅使用它来表达其他所有内容。 @zerkms 嗯,是的,我想你是对的。你可以返回一个“集合”,但是......嗯。想知道这是什么好方法。我想您正在返回完整转换的最终状态,即“一”状态。 它正在折叠 :-)(a -> b -> b) -> b -> [a] -> b
--- 这是来自 haskell 的签名。所以返回类型b
可以是任何东西,包括[a]
。所以出于这个原因,我有点不喜欢“reducer”这个词,因为它永远不会改变状态的形状(它可以但只有疯狂才会这样做)。
@zerkms 如果您认为可以添加有价值的信息并很好地教授概念,请随意编辑问题。不过,我认为将 OP 关于“reducer”术语的问题联系起来是很好的。如果您的编辑内容丰富,我会将其设为社区 wiki。
我同意 Saeed Neamati 的观点。这太可怕了。术语“减少”在用于其原始目的(即聚合)时是有意义的,但它不适用于不聚合的函数,而只是采用单个值并转换为新值。 React 的解释是,reducers “将一组动作(随着时间的推移)减少到一个状态”是一种荒谬的尝试,试图使该术语适合。【参考方案2】:
之所以将redux reducer 称为
reducer
,是因为您可以“减少”一个collection of actions
和一个initial state
(商店的)在其上执行这些操作以获得结果final state
.
怎么样?为了回答这个问题,让我再次定义一个 reducer:
reduce() 方法将
function (reducer)
应用于accumulator
,并且每个 数组的值(从左到右)将其减少为单个 价值。
redux reducer 是做什么的?
reducer 是一个纯
function
,它接受当前状态和 动作,并返回下一个状态。请注意,状态为accumulated
,因为集合上的每个操作都用于更改此状态。
因此,给定一个collection of actions
,reducer 将应用于集合的每个值(从左到右)。第一次,它返回initial value
。现在,reducer 再次应用于这个初始状态和返回下一个状态的第一个动作。并且每次在current state
上应用下一个集合项(操作)以获取next state
,直到它到达数组的末尾。然后,你会得到the final state
。太酷了!
【讨论】:
【参考方案3】:对不起,我不同意以前的答案。我不支持命名reducer
。我对 FP 和不变性充满热情。不要怪我,看第二部分,但我想先说明一下,为什么我不同意。
reducers 是转换序列是正确的,但序列本身 - 可能是另一个序列的一部分。想象一下,就像链接一样 - 链的一部分。但链条本身可能是更长链条的一部分。每一个环节都是全局状态的“过渡”。比,它背后的理论是什么?
它实际上不是“有限状态机”吗? - 关闭,但不是。实际上是Transition system。
标记的转移系统是一个元组 (S, Λ, →),其中 S 是一组状态,Λ 是一组标签,→ 是一组标记的转移
所以,S
- 是我们的状态集
Λ
- 是我们所谓的“行动”(理论上是标签)
...和
→
- reducers “标记转换”!如果我是这个库的创建者,我会这样命名。
理解这个理论帮助我实现了我的库,在那里我可以将低级转换系统作为高级转换系统的一部分(如链 - 仍然可能是更长链的一部分)-并且仍然具有单一的全局 Redux 状态。
【讨论】:
我一直在为图书馆苦苦挣扎,只是因为我无法通过“reducer”命名法……知道其他人也在为这个名字而苦苦挣扎,这实际上对我很有帮助。上面的@code4kix 定义也有帮助【参考方案4】:我的印象是 Redux 中的 reducer 负责修改状态。一个reducer示例:
const count = function(state, action)
if (action.type == 'INCREMENT')
return state + 1;
else if (action.type == 'DECREMENT')
return state - 1;
else
return state;
...我不明白这是一个如何传递给 reduce 的函数。这些数据是如何减少到单个值的?如果这是您将传递给 reduce 的函数,则 state 将是回调,而 action 将是初始值。
// count function from your question
const count = function (state, action)
if (action.type == 'INCREMENT')
return state + 1;
else if (action.type == 'DECREMENT')
return state - 1;
else
return state;
// an array of actions
const actions =
[ type: 'INCREMENT'
, type: 'INCREMENT'
, type: 'INCREMENT'
, type: 'INCREMENT'
, type: 'DECREMENT'
]
// initial state
const init = 0
console.log(actions.reduce(count, init))
// 3 (final state)
// (INCREMENT 4 times, DECREMENT 1 time)
【讨论】:
【参考方案5】:这些答案很好,但我认为它比您想象的要简单得多。我承认我一开始也有类似的困惑。 Javascript 数组的 reduce 方法接受一个累加器和一个回调函数。
const arr = [1, 2, 3]
const sum = arr.reduce((accumulator, element) =>
accumulator += element;
return accumulator;
); // sum equals 6 now
在redux中之所以叫reducer,是因为它的结构大致相似。
const sum = arr.reduce((accumulator, element) => // accumulator is the initial state
accumulator += element; // we do something to modify the initial state
return accumulator; // we return that and it becomes the new state
);
所以每次我们运行 reducer 时,我们都会接受一些东西,对其进行修改,然后返回相同内容的副本。在每次迭代中,我们都指向同一个东西。好的,是的,我们必须在redux中进行复制,以免直接修改状态,但象征性地每次运行它时,有点像上面例子中reduce从初始状态1开始的方式。然后我们添加2 到我们的初始状态并返回 3。现在我们再次运行我们的“减速器”,初始状态为 3,然后添加 3,最终得到 6。
【讨论】:
【参考方案6】:之所以称为reducer,是因为它是您将传递给Array.prototype.reduce(reducer, ?initialValue)的函数类型
Array.reduce
这与您将作为回调(reducer)传递给 Array.reduce 的内容非常相似。重要的部分是:
callback
Function to execute on each value in the array, taking four arguments:
previousValue
The value previously returned in the last invocation of the callback, or initialValue, if supplied. (See below.)
currentValue
The current element being processed in the array.
state 是“previousValue”,action 是“currentValue”。
【讨论】:
【参考方案7】:调用 Redux reducers reducers 语义不正确,没有多大意义。 这就是作者困惑的原因。
reducer 是一个函数,它将一组值缩减为单个值。
我们也可以说它折叠值——因此是函数式编程中经典的fold()
fn。
由于 Redux reducer 不折叠一组值,而是将动作应用于状态并始终返回相同的形状 (State -> Action -> State
) - 它应该被称为 应用程序 em> 或 应用程序。
但是,由于我们必须始终返回相同的状态 shape,而不仅仅是完全不相关的 - 我们调用 Redux 状态应用程序 changers 会更有意义,transformers 或 mutators。
而且,使用 'mutate the state' 和 'state mutator' 之类的术语确实已经司空见惯。
但 Redux 听起来比 Applux 或 Mutux 更酷:)
【讨论】:
【参考方案8】:这是一个令人困惑的名称,因为实际上它与 Array.prototype.reduce
无关。
在 Redux 源代码 here 中,你的 reducer 是这样调用的:
currentState = currentReducer(currentState, action)
使用该术语很诱人,因为您在任何时间点的状态都可以通过调用arrayOfPreviousActions.reduce(reducer)
找到。
['INCREMENT', 'INCREMENT'].reduce((currentState, action) => currentState++, 0)
但实际上并不是这样称呼的。
真正的问题是,给它取个更好的名字是什么?
如果我们不考虑输出/目的,它就是在处理一个动作/事件。一个actionHandler
,actionProcessor
。
除此之外:event
可能也比 action
更好,因为 Redux 也经常用于响应 API 响应,导致操作类型设置为 GET_USERS_SUCCESS
...这显然是一个事件.不过,这只是从 Flux 中采用的。
Redux 从Event Sourcing 模式和状态机/转换系统中汲取灵感。谷歌搜索发现经常使用的术语state transition
。
状态转换可以表示为接受状态和事件并产生新状态的函数
但是我们如何称呼这些“功能”呢?
恕我直言,reducer
是一个糟糕的名字,因为它没有编码关于其目的的任何含义,只是它的外观,但我想不出更好的名字。
最后,事物的所有名称都是在某个时候发明的,鉴于其受欢迎程度,reducer
现在与 state reducer
或 state transition function
相关联。
以argument
一词的由来为例:
“论证”一词的使用源自天文学,历史上使用表格来根据行星在天空中的位置来确定行星的空间位置。这些表格是根据被称为论据的测量角度组织的,字面意思是“阐明其他事物的东西”。
【讨论】:
【参考方案9】:这不是一个很具体的名字,但是 Redux 中的 reducer 和其他 reducer 函数做的事情是一样的。他们接受多种东西,并给我们一物。 Redux reducer 接受两件事(前一个状态和要对前一个状态执行的操作)并将这两件事简化为一个:下一个状态。
【讨论】:
【参考方案10】:Array.reduce
和 Redux 都采用“reducer”函数,将 previous
和 current
缩减为单个 return
值。
const reducer = (previous, current) =>
/* calculate value */
return value;
;
previous | current | return | |
---|---|---|---|
Array.reduce | accumulator | current element | new accumulator |
Redux | state | action | new state |
Redux docs:
如果我们要创建一个 Redux 操作数组,请调用
reduce()
,然后 传入 reducer 函数,我们会以同样的方式得到最终结果:const actions = [ type: 'counter/incremented' , type: 'counter/incremented' , type: 'counter/incremented' ] const initialState = value: 0 const finalResult = actions.reduce(counterReducer, initialState) console.log(finalResult) // value: 3
我们可以说Redux reducer 减少了一组动作(随着时间的推移) 进入单一状态。区别在于
Array.reduce()
吧 一次发生,而在 Redux 中,它发生在整个生命周期中 您正在运行的应用程序。
【讨论】:
以上是关于为啥 Redux reducer 被称为 reducer?的主要内容,如果未能解决你的问题,请参考以下文章
为啥我们在 Flux/Redux 架构中解耦 action 和 reducer?
为啥 CouchDB 中的 MapReduce 被称为“增量”?
为啥我的 Redux reducer 认为我的状态是未定义的?