为什么 Redux 的状态函数叫做 reducer?

Why are Redux's state functions called reducers?

这是a part the official Redux documentation:

It’s called a reducer because it’s the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue)

这对我来说意义不大。有人可以向我解释为什么它们实际上被称为减速器吗?它们 return 默认值(或者它们具有默认参数值)这一事实并不能使它们成为 reducers 恕我直言。

The fact that they return a default value (or they have a default argument value) doesn't make them reducers IMHO.

Reducer 只是 return 默认值。他们总是 return 状态的积累(基于所有以前和当前的动作)。

因此,它们充当状态的 reducer。每次调用 redux reducer 时,都会通过操作 (state, action) 传入状态。然后根据操作减少(或累积)此状态,然后return编辑下一个状态。这是经典 foldreduce 函数的一个循环。

正如@azium 用 state -> action -> state 总结的那样。

如果您将应用中的一系列操作视为一个列表,或者更像是一个流,那么它可能更有意义。

举这个人为的例子:

['apple', 'banana', 'cherry'].reduce((acc, item) => acc + item.length, 0)

第一个参数是 (Int, String) => Int 形式的函数。与初始值一起,您传递 reduce 可能称为 "reducer function" 的内容,您将获得处理一系列项目的结果。所以你可能会说,reducer 函数描述了对每个连续的单独项目做了什么来改变结果。换句话说,reducer 函数采用先前的输出和下一个值,并计算下一个输出。

这类似于 Redux reducer 所做的事情:它获取前一个状态和当前操作,然后计算下一个状态。

在真正的函数式编程风格中,您可以从概念上抹去应用于参数和结果的含义,而只关注输入和输出的 "shape"。

在实践中,Redux reducers 通常是正交的,在某种意义上,对于给定的操作,它们不会都对相同的属性进行更改,这使得拆分它们的职责和聚合输出变得容易 combineReducers.

作者认为state是reduce函数的累加器。例如:

Final State = [Action1, Action2, ..., ActionN].reduce(reducer, Initial State);

reduce函数来自函数式编程,名字"reducer"也来自FP。

我不喜欢在这里使用那个名字。因为我不认为世界是行动后的单一价值结果。这里的状态是一个对象。例如:

['eat', 'sleep'] === [addTodo('eat'), addTodo('sleep')].reduce(reducer, []);

这个 Reducer 根本不减少任何东西。而且我不在乎它是否减少任何东西。将其命名为 Transducer 会更有意义。

我们知道 Reducer 的来源(函数式编程),以及为什么它们可能被认为是在做减少工作(将 n 个输入项减少到单个 return 值——这正是普通函数的作用应该做)。 然而:名字只是一个名字,就像玫瑰是玫瑰的名字。不要想太多。 Redux 程序员是 IT 人员,他们被锁定在他们的上下文中,这是有道理的。我们其他人必须接受发明家将蓝狗称为黄猫的权利 ;-)

如前所述,该名称与函数式编程中的 reducer 概念有关。您可能还会发现 Merriam-Webster 词典对 reducer 的定义很有帮助:

1a. to draw together or cause to converge : consolidate (reduce all the questions to one)

reducer 将操作合并到表示应用程序状态的单个对象中。

The reason why a redux reducer is called a reducer is because you could "reduce" a collection of actions and an initial state (of the store) on which to perform these actions to get the resulting final state.

怎么样?为了回答这个问题,让我再次定义一个减速器:

The reduce() method applies a function (reducer) against an accumulator and each value of the array (from left-to-right) to reduce it to a single value.

redux reducer 有什么作用?

The reducer is a pure function that takes the current state and an action, and returns the next state. Note that the state is accumulated as each action on the collection is applied to change this state.

因此给定一个 collection of actions,reducer 应用于集合的每个值(从左到右)。第一次,它 return 是 initial value。现在 reducer 再次应用到这个初始状态,第一个动作到 return 下一个状态。并且每次在 current state 上应用下一个集合项(动作)以获得 next state 直到它到达数组的末尾。然后,你得到 the final state。多酷啊!

其他答案很好地解释了为什么它被这样命名,但让我们尝试命名更多的东西...

const origState = 0;
const actionOperators = {
    increment: (origState) => origState++,
    decrement: (origState) => origState--,
};
const anOperator = (aState, anAction) => actionOperators[anAction](aState);
const actions = ['increment', 'decrement', 'increment'];
const finalState = actions.reduce(anOperator, origState);

首先,reduce可以称为use anOperator with every action name and accumulated state, starting with origState。在 smalltalk 中它被称为 actions inject: origState into: anOperator。 但是实际上你向操作员注入了什么? origState 和动作名称。所以即使在Smalltalk中,方法名也不是很清楚。

actionOperators[increment] 是一个 Reducer,但我宁愿称它为 actionOperator,因为它是为每个操作实现的。状态只是一个参数(另一个参数是 return 值)。

不过,Reducer 是一个更好的词,可以放在 google 搜索结果的顶部。它也类似于 Redux。

我不太明白 Redux reducer 如何直接映射到你使用 reduce 的函数,所以这里有几个例子来了解它们是如何匹配的。

首先是来自 MDN Array.reduce documentation and then a simplified example of Dan Abramov's Counter.js at the end of his 'You might not need Redux' blog post.

的标准减速器(在 MDN 中称为 'accumulator')函数
  • sum 向累加器添加一个值
  • reducer adds/subtracts 一个值 to/from 累加器。

在这两种情况下,'state' 只是一个整数。

你正在'accumulating'把动作变成状态。这也是修改任何 JavaScript 对象的不可变方法。

const sum = function(acc, val) {
    return acc + val;
};

const reducer = function(state, action) {
    switch (action) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
};

console.log('sum', [1, -1, 1].reduce(sum, 0));

console.log('reduce', ['INCREMENT', 'DECREMENT', 'INCREMENT'].reduce(reducer, 0));

console.log('sum', [1, 1, 1].reduce(sum, 0));

console.log('reduce', ['INCREMENT', 'INCREMENT', 'INCREMENT'].reduce(reducer, 0));

在下面的代码中,您只需将累加器视为操作,将 currentValue 视为 redux 上下文中的状态。通过这个例子,你会发现为什么他们也将它命名为 reducer。

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;

// Main operation is 1 + 2 + 3 + 4 = 10
// but think of it as a stack like this: 

// | 2 | | 3 | | 4 | 
// |_1_| |_3_| |_6_| | 10 | => the 10 is in result

console.log(array1.reduce(reducer));
// expected output: 10

reduce() 方法在 数组的每个元素,产生一个输出值。

可以说,这是一个糟糕的命名选择。 (主观评论,没有冒犯,我同意命名很难,你不能同时让每个人都开心。)

我知道人们,仅仅因为这个命名,很难适应 redux。当我告诉他们时,这是一个简单的“状态转换器”,然后一切都神奇地变好了。因此,我们开始使用 stateChanger 作为缩减程序的函数名称,而不是使用 reducer 这个词。

知识问答:

从计算机科学的角度来看,归约是使用一个问题的算法来解决另一个问题(解决一个问题也可以解决另一个问题)。例如 SAT class 问题减少到 3SAT class。这意味着所有 SAT 问题都可以通过 3SAT class 问题的求解器来解决。 (丧钟为谁而鸣——或者只是感兴趣——你可以参考最伟大的活着的头脑Scott Aaranson's lecture on the subject)正如你所看到的,这里的减少与 redux 的减速器无关。

我们的做法:

另一方面,redux的游玩区更像是“状态机”。所以reducer实际上是一个状态改变器函数。这就是为什么在我们公司,我们使用 stateChanger 命名,以避免每次都考虑函数在做什么。 (是的,那几毫秒是我们试图避免的,因为它有时会让我们烦恼,因为我们失去了对主要问题的注意力。而且由于 redux 是表示层数据的核心,我们经常来到相同的位置并避免即使是毫秒对我们来说也是宝贵的。)

调用 Redux reducers reducers 在语义上是不正确的,没有多大意义。 这就是作者困惑的原因。

reducer 是一个函数,它将一组值缩减为单个值
我们也可以说它 折叠值 - 因此是函数式编程中的经典 fold() fn。

因为 Redux reducer 不会 折叠一组值,而是将一个动作应用于一个状态并且总是 return 相同的形状(State -> Action -> State ) - 它应该被称为 applicatorapplier.
但是,由于我们必须始终 return 相同的 shape 状态,而不仅仅是完全不相关的 smth - 我们调用 Redux state applicators [=20 更有意义=]变换器、变形器突变器.
而且,使用 'mutate the state''state mutator'.

之类的术语确实变得司空见惯

但是 Redux 听起来比 AppluxMutux 更酷:)