Javascript 使用Redux将业务逻辑放在一个地方

Javascript 使用Redux将业务逻辑放在一个地方,javascript,redux,Javascript,Redux,我喜欢Redux。它简单而强大。 但当我开始在现实世界中使用它时,一个建筑问题让我发疯 如何在一个地方找到我的业务逻辑 因为我有两个可能的地方可以找到它: 动作创造者(AC) 存储减速器(SR) [AC]->[Action]->[SR] 下面是3个示例。 例1和例2-在AC和SR同步场景中定位业务决策。 例3-在异步场景中在AC中做出的业务决策 在我的项目中,我注意到业务决策如何在AC和SR之间迅速分散。所以每次我想调试一些东西时,我都应该问自己——好的,那么我想检查的决定在哪里,AC还是S

我喜欢Redux。它简单而强大。
但当我开始在现实世界中使用它时,一个建筑问题让我发疯

如何在一个地方找到我的业务逻辑

因为我有两个可能的地方可以找到它:

  • 动作创造者(AC)
  • 存储减速器(SR)
[AC]->[Action]->[SR]

下面是3个示例。
例1和例2-在AC和SR同步场景中定位业务决策。
例3-在异步场景中在AC中做出的业务决策

在我的项目中,我注意到业务决策如何在AC和SR之间迅速分散。所以每次我想调试一些东西时,我都应该问自己——好的,那么我想检查的决定在哪里,AC还是SR?
从架构的角度来看,我更愿意按域划分BL,而不是按AC/SR

我的观点:虽然我了解纯减速机的优点,使热重新加载、时间旅行、撤销/重做功能成为可能,但我不确定我是否准备好用逻辑可维护性来换取它

不过,我和Redux只有一周的时间。
我错过了什么


示例1(同步,决策在reducer中):


示例2(同步,决策在行动中创建者):


示例3(异步,决策是活动的创建者):



您已经很好地介绍了各种可能性。不幸的是,没有一刀切的答案。这是你的应用程序,你必须做出决定


上的Redux常见问题解答对该主题有很好的引用,并链接到一些相关讨论。

我认为没有任何一个答案,但我已按目录对业务逻辑进行了分组。每个目录将包含1个操作文件(N个导出)和1个reducer文件(N个导出)。这也有助于思考随着时间的推移,你的行为将如何演变。我发现动作需要更多的时间来设计,因为它们现在或将来可能会有副作用。下面是Dan对此的看法:@MattLo,是的,我还按域对逻辑进行了分组,并为每个域(授权、实体等)设置了单独的文件夹,每个文件夹中有2个文件-AC和SR文件。确定。官方常见问题解答解决了我的确切问题。在我看来,回答:“是的,你有两个地方可以找到你的BL。现在你应该自己决定如何组织你的代码-(胖AC/瘦SR),或者反之亦然。”。1-我看不到选项执行(精简AC/fat SR)场景,因为我已经被迫在AC中定位我的异步操作,结果是(精简AC/fat SR)。2-理想情况下,我更喜欢在Redux之上构建一些框架,这将强制我和我的队友遵循一种具体的方法,而不是让开发人员来处理。是的,关于讨论这个问题,实际上还有很多评论。您可能可以在我的Redux库列表中找到相关内容。特别是“变体”页面有很多库,它们使用Redux作为基础,但从那里开始,它们朝着特定的方向发展。不过,我不确定你会发现任何严格执行绝对模式的东西。
// 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
  }
}
// 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
  }
}
// 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
  }
}