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

这里是 官方的 Redux 文档的一部分:

它之所以叫做约简函数,是因为它是你要传递的函数类型 到 Array.prototype.reduce(reducer, ?initialValue)

这对我来说没什么意义。有人能解释一下为什么它们实际上被称为减少器吗?事实上,它们返回一个默认值(或者它们有一个默认参数值)并不会使它们成为减法器 IMHO。

21519 次浏览

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

Reducers do not just return default values. They always return the accumulation of the state (based on all previous and current actions).

Therefore, they act as a reducer of state. Each time a redux reducer is called, the state is passed in with the action (state, action). This state is then reduced (or accumulated) based on the action, and then the next state is returned. This is one cycle of the classic fold or reduce function.

As @azium summed up with state -> action -> state.

If you consider the series of actions in your app to be like a list, or maybe more like a stream, it might make more sense.

Take this contrived example:

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

The first argument is a function of the form (Int, String) => Int. Along with an initial value, you pass reduce what might be called a "reducer function", and you get the result of processing the series of items. So you might say, the reducer function describes what is done with each successive individual item to change the result. In other words, the reducer function takes the previously output and the next value, and it calculates the next output.

This is analogous to what a Redux reducer does: it takes the previous state and the current action, and it calculate the next state.

In true functional programming style, you can conceptually erase the meaning applied to the arguments and the result, and just focus on the "shape" of the inputs and output.

In practice, Redux reducers are typically orthogonal, in the sense that for a given action, they don't all make changes to the same properties, which makes it easy to split their responsibilities and aggregate the output with combineReducers.

The author think state as the accumulator of the reduce function. Ex:

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

The reduce function is coming from Functional Programming, the name "reducer" is also coming from FP.

I don't like using that name here. Because I don't see the world as a single value result after actions. The state here is an object. For example:

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

This Reducer reduces nothing at all. And I don't care it reduce anything or not. Naming it as Transducer will make more sense.

We know where Reducers are coming from (functional programming), and why they might be considered to be doing reducing work (reduce n input items to a single return value - which is just what normal functions are supposted do). However: The name is just a name, like rose is the name for a rose. Do not think too much. Redux progammers are IT people, they are locked in their context and there it makes sense. The rest of us have to accept the inventor's right to call a blue dog a yellow cat ;-)

As already mentioned the name is related to the concept of a reducer in functional programming. You may also find the Merriam-Webster dictionary definition of reducer helpful:

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

The reducer consolidates actions into a single object representing application state.

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.

How? To answer that, let me define a reducer again:

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.

And what does a redux reducer do?

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.

So given a collection of actions, the reducer is applied on each value of the collection (from left-to-right). The first time, it returns the initial value. Now the reducer is applied again on this initial state and the first action to return the next state. And the next collection item (action) is applied each time on the current state to get the next state until it reaches the end of the array. And then, you get the final state. How cool is that!

Other answers explain well why it's named as it is but let's try naming more things...

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);

First, reduce could be called use anOperator with every action name and accumulated state, starting with origState. In smalltalk it's called actions inject: origState into: anOperator. But what do actually you inject into the operator? The origState AND the action names. So even in Smalltalk method names are not very clear.

actionOperators[increment] is a Reducer, but I would rather call it and actionOperator because it's implemented for each action. The state is just an argument (and another one as return value).

Reducer is however a better word to be on top of google search results. It's also similar to Redux.

I couldn't quite see how a Redux reducer directly mapped to the function you use with reduce, so here's a couple of examples to see how they match up.

First a standard reducer (called 'accumulator' in MDN) function from the 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.

  • sum adds a value to the accumulator
  • reducer adds/subtracts a value to/from the accumulator.

In both cases here the 'state' is just an integer.

You are 'accumulating' the actions into the state. This is also the immutable way to modify any JavaScript object.

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));

In this code below you just need to think of the accumulator as the actions and currentValue as a state in redux context. with this example you will find out why they name it as a reducer too.

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

The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.

It is, arguably, a poor choice of naming. (Subjective comment, no offence, I agree naming is hard and you can not make everybody happy at the same time.)

I know people, just because of this naming, had hard time to adapt the redux. When I told them, it is a simple "state changer" then everything magically became fine. So rather than using the word reducer, we started using stateChanger as the reducers' function names.

Trivia:

From computer science perspective, a reduction is employing an algorithm of a problem to solve another problem (solving one can solve the other as well). For example SAT class problems reduce to the 3SAT class. It means all SAT problems can be solved by a solver of the 3SAT class problems. (For whom the bell tolls - or simply interested - you can refer to the greatest living mind Scott Aaranson's lecture on the subject) As you see the reducing here has nothing to do with redux's reducers.

Our approach:

On the other hand, redux's play area is more like the "state machines". So a reducer is actually a state changer function. That is why we, in our company, are using the stateChanger naming in order to avoid thinking about what the function was doing each time. (Yes that few milliseconds are what we are trying to avoid, because it sometimes annoys us because we lose our concentration on the main problem. And since redux is the heart of the data on the presentation layer, we came the same position frequently and avoiding even milliseconds is valuable for us.)

Calling Redux reducers reducers is semantically incorrect and doesn't make much sense. That's why the author is confused.

A reducer is a function that reduces a set of values to a single value.
We can also say that it folds the values - thus the classic fold() fn in functional programming.

Since Redux reducer does not fold a set of values, but applies an action to a state and always returns the same shape (State -> Action -> State) - it should be called applicator or applier.
But, since we have to always return the same shape of the state, and not just smth completely unrelated - we'd make much more sense calling Redux state applicators changers, transformers or mutators.
And, it indeed has become commonplace to use terms like 'mutate the state' and 'state mutator'.

But Redux sounds just so much cooler, than Applux or Mutux :)