简体   繁体   中英

Put business logic in one place with Redux

I like Redux. It is simple and powerful.
But when I started using it in real word, one architectural question drives me mad.

How to locate my business logic in one place?

Because I have 2 possible places where to locate it:

  • Action Creators (AC)
  • Store Reducers (SR)

[AC] -> [Action] -> [SR]

Below is 3 examples.
Ex. 1 and 2 - locate business decisions in AC and SR in sync scenario.
Ex. 3 - business decision made in AC in async scenario.

In my project I've noticed how business decisions are getting scattered between AC and SR very quickly. So each time I want to debug something I should ask myself - ok, so where that decision I want to check is located, AC or SR?
From architectural point of view, I'd rather want to split BL by domains, not by AC/SR.

My point : while I understand advantages of pure reducers that make hot-reloading, time-travel, undo/redo features possible, I'm not sure I'm ready to trade logic maintainability for that.

Still, I have only one week with Redux.
What have I missed?


Example 1 (sync, decisions are in reducer):

// action-creators.js

export function increment() {
  return {
    type: 'INCREMENT'
  }
}

export function decrement() {
  return {
    type: 'DECREMENT'
  }
}

// counter-reducer.js

export default function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1  // (!) decision of how to '(in|de)crement' is here
  case 'DECREMENT':
    return state - 1
  default:
    return state
  }
}

Example 2 (sync, decisions are in action creators):

// action-creators.js

export function increment() {
  return (dispatch, getState) => {
    return {
      type: 'CHANGE_COUNTER',
      newValue: getState() + 1 // (!) decision of how to '(in|de)crement' is here
    }
  };
}

export function decrement() {
  return (dispatch, getState) => {
    return {
      type: 'CHANGE_COUNTER',
      newValue: getState() - 1
    }
  };
}

// counter-reducer.js

export default function counter(state = 0, action) {
  switch (action.type) {
  case 'CHANGE_COUNTER':
    return action.newValue
  default:
    return state
  }
}

Example 3 (async, decisions are in action creators):

// action-creators.js

export function login() {
  return async (dispatch, getState) => {
    let isLoggedIn = await api.getLoginState();

    if (!isLoggedIn) {  // (!) decision of whether to make second api call or not
      let {user, pass} = getState();
      await api.login(user, pass)
    }

    dispatch({
      type: 'MOVE_TO_DASHBOARD'
    })
  };
}

// some-reducer.js

export default function someReducer(state = 0, action) {
  switch (action.type) {
  case 'MOVE_TO_DASHBOARD':
    return {
      ...state,
      screen: 'dashboard'
    }
  default:
    return state
  }
}

You've pretty well covered the possibilities. Unfortunately, there is no one-size-fits-all answer. It's your app, you'll have to decide.

The Redux FAQ answer on structuring business logic has a good quote on the topic, and links to a few related discussions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM