最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Why do you need 'Actions' as data in redux? - Stack Overflow

programmeradmin2浏览0评论

Redux documentations says I should make actions and action creators, like this:

function addTodo(filter) {
  return { 
    type: SET_VISIBILITY_FILTER, 
    filter
  }
}

Then write reducers, like this:

function todoApp(state = initialState, action) {
  switch (action.type) {
   case SET_VISIBILITY_FILTER:
     return Object.assign({}, state, {
       visibilityFilter: action.filter
     });
  } 
}

I then invoke the action using dispatch:

store.dispatch(addTodo("Ask question on stackoverflow"));

It seems there's a one-one correspondence between actions and reducers; the sole purpose of the action is to select a reducer and provide input data for that reducer.

Why don't we skip the middle man and identify actions with reducers and action creators with functions producing reducers? Then dispatch would take a single argument, a reducer/action of type State => State:

// Action/reducer. (Parametrised state transformer, really.)
const addTodo = text => state => {
  return Object.assign({}, state, {
       visibilityFilter: action.filter
  });
}

// Dispatch takes as a argument the action/reducer
store.dispatch(addTodo("Ask question on stackoverflow"));

You'd lose the ability to serialise actions, but otherwise, it seems you'd get rid of boilerplate action creators and express more clearly the connection between actions and reducers. If you're in Typescript, you also get typechecking of the data in actions, which is difficult to express otherwise.

So what reasons for having actions as data am I missing?

Redux documentations says I should make actions and action creators, like this:

function addTodo(filter) {
  return { 
    type: SET_VISIBILITY_FILTER, 
    filter
  }
}

Then write reducers, like this:

function todoApp(state = initialState, action) {
  switch (action.type) {
   case SET_VISIBILITY_FILTER:
     return Object.assign({}, state, {
       visibilityFilter: action.filter
     });
  } 
}

I then invoke the action using dispatch:

store.dispatch(addTodo("Ask question on stackoverflow"));

It seems there's a one-one correspondence between actions and reducers; the sole purpose of the action is to select a reducer and provide input data for that reducer.

Why don't we skip the middle man and identify actions with reducers and action creators with functions producing reducers? Then dispatch would take a single argument, a reducer/action of type State => State:

// Action/reducer. (Parametrised state transformer, really.)
const addTodo = text => state => {
  return Object.assign({}, state, {
       visibilityFilter: action.filter
  });
}

// Dispatch takes as a argument the action/reducer
store.dispatch(addTodo("Ask question on stackoverflow"));

You'd lose the ability to serialise actions, but otherwise, it seems you'd get rid of boilerplate action creators and express more clearly the connection between actions and reducers. If you're in Typescript, you also get typechecking of the data in actions, which is difficult to express otherwise.

So what reasons for having actions as data am I missing?

Share Improve this question edited Jan 13, 2016 at 7:12 Søren Debois asked Jan 13, 2016 at 5:30 Søren DeboisSøren Debois 5,6881 gold badge28 silver badges50 bronze badges 1
  • Added some notes in my answer after reading other answers. Don't know if it helps for TypeScript (may be you need to provide some code examples to show exact problem). – Dmitry Yaremenko Commented Jan 13, 2016 at 19:57
Add a comment  | 

3 Answers 3

Reset to default 9

The main purpose of action in Redux is to reduce the state. Reduce method will be called on array of actions (thats why it called a reducer). Example:

import reducer from './reducer';

const actions = [
    {type: 'INIT'},
    {type: 'SOME_ACTION', params: {...}},
    {type: 'RECEIVE_DATA', data: [...]},
    {type: 'SOME_ANOTHER_ACTION', params: {...}},
    {type: 'RECEIVE_DATA', data: [...]},
    ...
];

const finalState = actions.reduce(reducer, undefined);

Action creators is a function that can create actions. It is not necessary that action creator will create only one action.

Actually, if your reducer is able to receive functions instead of objects - your actions will be functions and it will do the main purpose, but you can loose some benefits of Redux functional abilities.

In that case the reducer will be implemented like this:

function reducer(state, action) {
    return action(state);
}

The reasons why you may create actions as {type: 'ACTION_NAME'} format:

  1. Redux DevTools expects this format.
  2. You need to store sequence of actions.
  3. Reducer makes state transformations on worker.
  4. Everybody in Redux ecosystem use this format. It's a kind of convention.
  5. Hot reloading abilities (your stored functions will not be reloaded).
  6. You need to send actions as is on server.
  7. Debugging benefits - to see the stack of actions with actions names.
  8. Writing unit tests for reducer: assert.equal(finalState, expectedState).
  9. More declarative code - action name and parameters are about "what to do" and not about "how to do" (but addTodo('Ask question') is declarative too).

Note about coupling between action creators and state changes

Just compare two notations:

First:

function someActionCreator() {
    return {
        type: "ADD_TODO",
        text: "Ask question on stackoverflow"
    }; // returns object
}

Second:

function someActionCreator() {
    return addTodo("Ask question on stackoverflow"); // returns function
}

"In both cases we see that code is declarative and action creator is decoupled from state change. You can still reuse addTodo or dispatch two addTodo's or use middleware or dispatch compose(addTodo('One'), addTodo('Two')). The main difference is that we created Object and Function and place in code where state changes.

There is NOT a one-to-one mapping between actions and reducers. Per Dan Abramov's comments at https://github.com/Pitzcarraldo/reduxible/issues/8 :

It reinforces a very common misconception about Redux: namely that action creators and reducers are one-to-one mapping.

This is only true in trivial examples, but it is extremely limiting in real applications. Beginners exposed to this pattern couple reducers and action creators, and fail to realize they're meant to be many-to-many and decoupled.

Many reducers may handle one action. One reducer may handle many actions. Putting them together negates many benefits of how Flux and Redux application scale. This leads to code bloat and unnecessary coupling. You lose the flexibility of reacting to the same action from different places, and your action creators start to act like “setters”, coupled to a specific state shape, thus coupling the components to it as well.

As for actions and the "type" parameter, the other answers are right. That's deliberately how Redux was designed, and that was intended to give the benefits of serialization for debugging purposes.

Good question.

Separating actions from state changes is really a Flux pattern, rather than a specifically Redux thing. (Though I will answer the question with reference to Redux.) It's an example of loose coupling.

In a simple app, tight coupling between actions and state changes might be fine. But in a larger app, this could be a headache. For instance, your addTodo action might trigger changes in several parts of the state. Splitting actions from state changes - the latter performed in reducers - allows you to write smaller functions, which are easier to reason about and more testable.

Additionally, decoupling your actions and state changes allows your reducer logic to be more reusable. e.g. Action X might trigger state changes A and B, whilst action Y only triggers state change A.

Furthermore, this decoupling gives rise to a Redux feature called middleware. Middleware listens to action dispatches. It doesn't change the state of the app, but it can read the current state and the next state, as well as the action information. Middleware is useful for functionality extraneous from the core application logic, e.g. logging and tracking (dev tools were mentioned in a previous answer).

[UPDATE] But why have actions as objects?

If it were simply a matter of functions calling other functions, that decoupling would be much less explicit. Indeed it may even get lost; since most actions only enact one state change, developers might tire of a single function calling a single function and do away with the separation entirely.

The other thing is the Flux data flow model. One-way data flow is very important to the Flux/React paradigm. A typical Redux/React goes something like this: Store state -> Higher order React components -> Lower order React components -> DOM. The action is the aberration in the model; it's the backwards arrow transmitting data from the view to the store. It makes sense to make the action something as loud and emphatic as possible. Not a mere function call, but a dispatched object. It's as if your app were announcing Hey! Something important happened here!

发布评论

评论列表(0)

  1. 暂无评论