actions 还是 reducer 哪个更简单?

Which should be simpler, actions or reducers?

这是使用 thunk 时编写操作的一种方式,这导致 reducer 非常简单。

   getCurrentUserPicture(){
        return (dispatch,getState) => {
            dispatch({type: "isloading", isLoading: true}); // shows a loading dialog
            dispatch({type: "errorMessage"}); // clear errorMessage
            dispatch({type: "warningMessage"}); // clear warningMessage

            const userId = getState().user.get("currentUser").id;
            getUserPicture(userId) // get from backend
                .then(picture => {
                    dispatch({type: "userPicture", picture});
                    dispatch({type: "isLoading", isLoading: false}); 
                }
            )
            .catch(e=>{
                dispatch({type: "errorMessage", e});
                dispatch({type: "isLoading", isLoading: true});
                }
            )
        }
    }

减速机包括:

export reducer(state = initialState, action = {}) {
    switch(action.type) {
        case "isLoading":
            return state.set("isLoading", action.isLoading)

这是另一种方法,其中的操作是 "cleaner" 但 reducers 更复杂:

   getCurrentUserPicture(){
        return (dispatch,getState) => {
            dispatch({type: "gettingCurrentUserPicture", true});

            const userId = getState().user.get("currentUser").id;
            getUserPicture(userId)
                .then(picture => {
                    dispatch({type: "retrievedCurrentUserPicture", picture}); 
                }
            )
            .catch(e=>{
                dispatch({type: "errorRetrievedCurrentUserPicture", e});
                }
            )
        }
    }

在上述操作的减速器中,您将拥有例如:

export reducer(state = initialState, action = {}) {
    switch(action.type) {
        case "gettingCurrentUserPicture":
            return state.set("isLoading", true)
                        .delete("errorMessage")
                        .delete("warningMessage")

一种方法比另一种更好吗?

无论哪种方式,都有 advantages/disadvantages。但我的选择是让 reducers 比 actions/actions creators 更简单

在 Reducers 中处理业务逻辑(使操作更简单)

在你的减速器中执行你所有的同步业务逻辑,让actions/action创作者更简单。

优势

  1. 根据您的业务逻辑,您可以决定下一个应用的状态。 应该是。
  2. 在 reducer 中处理业务逻辑很容易。

缺点

  1. 只能执行同步任务(但是有中间件 支持异步任务)。
  2. 无法访问 dispatch
  3. 如果您拆分了减速器,则无法访问整个应用程序状态。

在 Action Creator 中处理业务逻辑(使 reducer 更简单)

您可以简单地执行一些业务逻辑并在需要时触发操作。

优势

  1. 您可以访问 dispatch
  2. 您可以执行异步任务(请参阅 redux-thunk).
  3. 动作创建者可以访问整个状态 (redux-thunk),即使您已合并 减速器。

缺点

  1. 没有简单的方法可以让业务逻辑基于所有 动作。例如,如果您要为所有操作附加 ID, 那么你必须有一个附加所有动作的函数

Here详细讨论

如果你的 actionCreators 很复杂,调用很多小动作的小分派,你最终会慢慢地朝着你的状态有效地移动 'setters'(每个动作变成一个特定的 setter场地)。在 actionCreator 具有广泛影响的情况下,您最终不得不分派 'setter' 与各种不同的 sub-reducer 相关的操作——您将这些不同的 sub-reducer 的领域知识推送到 action creator 中。当你改变一个 sub-reducer 时,你将不得不找到所有调度相关动作的动作创建者并适当地调整它们。

通过仅指示发生了什么的胖操作(例如已发出请求、已返回成功响应、已触发计时器、已单击按钮),您可以管理对业务逻辑的更改,而无需改变 actionCreators。例如,您决定要在操作期间开始显示加载掩码 - 您现在不需要更新所有操作创建器来触发 loadingStart 和 loadingEnd 操作,您只需更新您的 reducer 以适当地设置加载状态。

就我个人而言,我认为复杂应用程序的最佳模型是使 actionCreators 变得微不足道(或者完全放弃它们,直接从连接的组件中分派动作有效负载),而是使用 redux-saga(https://github.com/yelouafi/redux-saga ) 来处理你的异步和不纯的东西——这是一个更强大的模型。例如,它可以轻松执行去抖动和节流操作、对存储状态的变化以及操作等做出反应。

我同意@TomW 的观点,即分派许多小动作会导致动作成为设置器。

动作创建者最初只是非常简单的函数,它们形成非常简单的对象,然后被调度。

调度一个动作意味着将它发送到管道中进行处理。 action的执行应该是reducer的责任。操作本身不应执行。相反,它应该是需要完成的事情的简单描述符。

异步动作创建器是事后才想到的,需要安装售后零件才能工作。它应该是异步的,并且尽可能坚持这样的想法,即动作应该在 reducer 中执行 并且 动作对象是动作的描述。

你可以给别人反汇编并称之为源代码。当然,它是源代码。但它的级别比真正的来源低得多,包含的信息也少得多。原始陈述是高层次的,因此包括有关意图的信息,而不仅仅是有关如何达到目的的信息。例如,反汇编中不存在变量名。

以同样的方式,调度许多本身不能传达意图的动作,不适合 Redux。

事实上,操作应始终描述意图并尽可能多地省略实施细节。

Redux 不只是 apply 操作。中间件可以记录它们并偿还它们。您可以检查历史记录并查看发生了什么。

如果动作不包括意图,或者它们存在的原因,那么历史只是一组有序的变化,动作类型可以简单地通过比较 2 个连续状态并检查什么 属性 改变了。这只是表明动作类型最终变得毫无意义。动作类型应该包含大部分含义。

异步动作创建者有点笨拙。被分派的东西不是像 Redux 任务那样的 pojo,而是一个函数。如果您使用 Redux 开发工具,则不会记录正在分派的事实。它不能重播。因此,异步操作创建者不会创建操作。

此外,查找无效状态应该允许您找到将那个状态放在那里的操作,并从那里找到处理该操作的代码。这是减速器代码。但是,如果真正的问题不在 reducer 中,而是在调度 action 的 action creator 中,那么就很难找到,因为不清楚哪个调度了 action。

(Ab)使用异步操作来实现业务逻辑意味着放弃使用 Redux 的一些最重要的好处。

为什么不两者兼而有之?

ActionsReducers 都应该尽可能简单。

这说起来容易做起来难,但是通过引入其他概念和模式,例如 selectorssagas 甚至简单的 utility functions/classes,在action和reducer上都可以大大降低复杂度。

操作

我知道 "broadcast" 这个词在不断变化的世界中确实不受欢迎 - 但我发现它可以帮助我完善我的行为中应该属于什么。该操作应该 "broadcast" 向世界展示刚刚发生的事情——它个人并不关心接下来会发生什么,或者有多少 reducer 选择响应它——它只是一个信使。即:它不关心应用程序在执行上述操作后的样子。

我的意见是业务 logic/rules 要么直接属于这里,要么可以通过辅助实用程序功能引导到这里。请参阅以下有关异步和实用程序的部分。

It should describe what happened. Describe, describe, describe

减速机

Reducer 应该使用尽可能少的数据来完整(大概)表示您的应用程序。在可能的情况下,保持状态树规范化以确保您保持最小状态。

我的意见是,这些应该尽可能轻,最多应该只是基本的对象操作——adding/removing键或更新值。

What should the state look like based on the description I just received? (from the action)

方法

这听起来很疯狂,但是 rubber duck debugging(在本例中是橡皮鸭编程)确实有助于规划你的 redux 结构。

我会按字面意思(有时甚至大声)通过以下步骤说话,例如:

  • You hover over the post title and click edit
  • The app swaps to the "edit post" page
  • You can edit the title field (and the field will update)
  • You can edit the body field (and the field will update)
  • The app will save every 90 seconds as a draft, a little save icon will appear in the upper right during the auto saves, but you can continue to work
  • You can also save by clicking the save button at anytime. During this time you won't be able to edit, and you will be redirected to the post index page

这可以松散地转化为动作和缩减器,通过查看描述的内容以及结果是状态的变化:

  • 点击编辑:操作 - 我们正在描述应用程序,点击了 ID X 的 post
  • 切换到 "edit post" 页面:Reducer - 当我们 "hear" a "post edit" 更改应用程序的状态时,它现在呈现 post 编辑页面
  • 编辑title/body:操作 - 我们描述用户输入了哪个字段和什么值。
  • 更新 title/body:Reducer - 应用的状态根据输入的字段而改变
  • 自动保存:Action/reducer/action/reducer
    • 行动:我们描述自动保存已经开始的应用程序
    • Reducer:应用程序状态更改以显示正在进行的自动保存
    • 行动:我们描述自动保存已完成
    • Reducer:应用状态更改以隐藏正在进行的自动保存

尽管冗长冗长,但我想表达的观点是,这不是 应该 更简单的,它是事物(某种)所属的地方,但很可能你会看到您的操作最终变得更加复杂,而您的减速器则不那么复杂。

可是,我的动作还是不瘦..

以上所有说起来都很容易,但是您如何保持这些商业行为 clean/slim/light/simple/etc?

归根结底,大多数业务逻辑与 React/Redux 没有太多关系 - 因此通常可以将它们放入实用函数或 classes 中。这有几个原因很棒 a) 更容易测试,因为它没有与 React/Redux 的零链接 b) 使您的操作轻松,c) 更加封装 - 对 react/redux 的了解很少一件坏事。

归根结底只是 Javascript - 导入自定义业务 classes 或实用程序函数没有任何问题。

但是但是,异步呢..

异步通常会很快开始使操作变得混乱。如果你想清理你的动作,Saga 真的值得一看,但如果你的团队还没有跨越生成器等等,那么一定要引入一定的学习曲线。在这种情况下,thunk 仍然有用,并且请记住,您可以将多个异步函数捆绑到一个 thunk 可以发挥作用的单一 promise(同样,该分组的 promise 可以分离成一个效用函数 class - 比如generateSaveSequencePromise() 这可能会结束 4 或 5 个异步获取,并且 return 一个承诺)。

旁注 - 您应该像第一个示例一样尽量减少从单个流中多次分派。如果可能,请尝试创建一个将整个操作组合为一个的父操作。所以使用你的第一个例子,它可能是:

// action
dispatch({type: "FETCHING_USER_IMAGE", id: userId });

然后你的各种 reducer 应该清除它们的消息队列,或者如果它们 "hear" 那个类型通过了。