【问题标题】:Which should be simpler, actions or reducers?哪个应该更简单,actions 还是 reducers?
【发布时间】:2017-04-15 20:11:49
【问题描述】:

这是在使用 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)

这是另一种方法,其中操作“更干净”但减速器更复杂:

   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")

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

【问题讨论】:

  • 无论如何,第二种方法似乎更常见。
  • 这个问题的赏金怎么了?

标签: reactjs redux immutability redux-thunk


【解决方案1】:

为什么不两者兼而有之?

ActionsReducers 都应该尽可能简单。

说起来容易做起来难,但是通过引入其他概念和模式,例如选择器sagas甚至是简单的实用程序函数/类 ,可以大大降低动作和reducers的复杂性。

动作

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

我的观点是,业务逻辑/规则要么直接属于这里,要么可以通过辅助实用程序函数在此处引导。请参阅下面有关异步和实用程序的部分。

它应该描述发生了什么。 描述、描述、描述

减速器

Reducer 应该使用尽可能少的数据来形成您应用的完整(ish)表示。在可能的情况下,保持你的状态树标准化,以确保你保持最小状态。

我的意见是,这些应该尽可能轻,最多应该只是基本的对象操作 - 添加/删除键或更新值。

根据我刚刚收到的描述,该状态应该是什么样的? (来自行动)

方法

这听起来很疯狂,但rubber duck debugging(在这种情况下是橡皮鸭编程)确实有助于规划您的 redux 结构。

我会通过以下步骤逐字逐句(有时甚至大声说出来),例如:

  • 您将鼠标悬停在帖子标题上并点击编辑
  • 应用切换到“编辑帖子”页面
  • 您可以编辑标题字段(该字段会更新)
  • 您可以编辑正文字段(该字段会更新)
  • 应用会每 90 秒保存一次草稿,自动保存时右上角会出现一个小保存图标,但您可以继续工作
  • 您也可以随时单击保存按钮进行保存。在此期间您将无法编辑,您将被重定向到帖子索引页面

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

  • 点击编辑:操作 - 我们描述该应用程序点击了 id X 的帖子
  • 切换到“编辑帖子”页面:Reducer - 当我们“听到”“帖子编辑”时会更改应用的状态,因此它现在会显示帖子编辑页面
  • 编辑标题/正文:操作 - 我们描述用户输入的字段和值。
  • 更新标题/正文:Reducer - 应用的状态会根据输入的字段而改变
  • 自动保存:动作/减速器/动作/减速器
    • 操作:我们描述自动保存已开始的应用
    • Reducer:应用状态更改以显示自动保存正在进行中
    • 操作:我们描述自动保存已完成
    • Reducer:应用状态更改以隐藏正在进行的自动保存

我想表达的意思是,它不是应该更简单的,它应该是事物(某种)所属的地方,但很可能你会看到你的行动结束了减少你的减速器变得更复杂。

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

以上所有内容都很容易说,但是您如何保持这些业务操作干净/苗条/轻巧/简单/等?

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

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

但是,异步呢..

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

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

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

然后你的各种 reducer 应该清除他们的消息队列,或者如果他们“听到”该类型通过的话。

【讨论】:

    【解决方案2】:

    我同意 @TomW 的观点,即调度许多小动作会导致动作成为 setter。

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

    调度动作意味着将其发送到管道中以进行处理。动作的执行应该是reducer的责任。操作本身不应执行。相反,它应该是需要做什么的简单描述。

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

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

    同样地,调度许多本身并不能传达意图的操作也不适合 Redux。

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

    Redux 不仅仅应用 动作。中间件可以记录它们并偿还它们。您可以检查历史记录并了解发生了什么。

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

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

    此外,查找无效状态应该可以让您找到将状态放置在那里的操作,并从那里找到处理该操作的代码。这是减速器代码。但是,如果真正的问题不是在 reducer 中,而是在派发动作的动作创建者中,那么将很难找到,因为不清楚是哪个派发了动作。

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

    【讨论】:

      【解决方案3】:

      如果你的 actionCreators 很复杂,调用了很多小动作的小调度,你最终会慢慢地向你的状态移动,有效地拥有“设置器”(每个动作都只是特定字段的设置器)。在 actionCreator 具有广泛影响的地方,您最终不得不调度与各种不同的 sub-reducer 相关的“setter”动作 - 您将这些不同的 sub-reducer 的领域知识推送到动作创建者中。当你更改一个 sub-reducer 时,你必须找到所有调度相关动作的动作创建者并适当地调整它们。

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

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

      【讨论】:

        【解决方案4】:

        无论哪种方式都有优点/缺点。但我的选择是让reducers 比actions/actions 创建者更简单

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

        在 reducer 中执行所有同步业务逻辑,让动作/动作创建者更简单。

        优势

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

        缺点

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

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

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

        优势

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

        缺点

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

        Here详细讨论。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-01-16
          • 1970-01-01
          • 2011-01-22
          • 2011-10-17
          • 1970-01-01
          • 2017-09-27
          • 2020-03-20
          • 2010-12-27
          相关资源
          最近更新 更多