reduxjs / redux

A JS library for predictable global state management
https://redux.js.org
MIT License
60.88k stars 15.27k forks source link

Reducer without switch #1167

Closed mapreal19 closed 8 years ago

mapreal19 commented 8 years ago

As reading from the docs you could avoid using switch with a function that maps action types to handlers. But still I'm not fully convinced with that approach.

In order to avoid those long switch statements, I suggest injecting the dependency of the reducers right into the actions.

The actions now will know how to change the state per each reducer. The reducer will just call them accordingly. This way, the reducer would follow the Open/Closed principle (Motivation by Uncle Bob: https://youtu.be/TMuno5RZNeE?t=3605)

I found this architecture much better. Take the case your system requires a new feature where 3 reducers listen to the same action. Then you would need to modify those reducers -- adding a new case on each switch statement.

Following the style I propose, you would only need to create the action with the reducers all in the same place. Example:

// ACTION
function addTodo() {
  reducers: {
    counterReducer: (state) => ...,
    todosReducer: (state) => ...,
    dummyReducer: (state) => ...
  }
}

// todos REDUCER
function todos(state = initialState, action) {
  if (action.reducers && typeof action.reducers.todosReducer === 'function') {
    return action.reducers.todosReducer(state);
  } else {
    return state;      
  }
}

Here you could see a refactor of the todomvc example using this idea.

Thoughts?

imevro commented 8 years ago

redux-actions and handleActions.

gaearon commented 8 years ago

The whole point of Flux/Redux is to decouple actions and reducers. There may be many independent reducers handling one action, and there may be many independent actions handled by one reducer. This is what makes Flux/Redux scale because big teams can work on overlapping features without constant merge conflicts, as state mutation logic is kept separate even if it is caused by the same actions.

You also lose the ability to serialize and record/replay actions because they are not plain objects any more in your example. Enabling this was another big constraint of Redux. Please read this:

There are frameworks claiming to be similar to Flux, but without a concept of action objects. In terms of being predictable, this is a step backwards from Flux or Redux. If there are no serializable plain object actions, it is impossible to record and replay user sessions, or to implement hot reloading with time travel. If you’d rather modify data directly, you don’t need Redux.

gaearon commented 8 years ago

Think of action as a "message". The action doesn't know how the state changes. It's precisely reducers' job. Otherwise your reducers don't seem to contain code at all. Also don't forget that reducers can be composed further than a single level. What you propose doesn't work with reducer composition because you're effectively hardcoding reducer structure into the action objects.

mapreal19 commented 8 years ago

I see. Thank you for the quick feedback @gaearon :+1:

negamaxi commented 6 years ago

Well, personally I hate switch so I use plain objects instead:


const actionHandlers = {
  [ADD_TODO] (state, todo) {
    // do stuff
  },
  [REMOVE_TODO] (state, id) {
    // do stuff
  }
}

const reducer =  (state = initialState, action) => {
    const { type, payload } = action
    const actionHandler = actionHandlers[type]
    if (actionHandler) {
      return actionHandler(state, payload)
    }
    return state
  }
batcer commented 5 years ago

@negamaxi What does this syntax mean that you use?

[ADD_TODO] (state, todo) {
    // do stuff
  },
negamaxi commented 5 years ago

@batcer it's a way to create a method of a certain name stored in a constant:

const ADD_TODO = 'addTodo';

const handlers = {
  [ADD_TODO] () {}
}

...is the same as...


const handlers = {
  addTodo() {}
}