我应该使用一种或几种动作类型来表示这个异步动作吗?

Posted

技术标签:

【中文标题】我应该使用一种或几种动作类型来表示这个异步动作吗?【英文标题】:Should I use one or several action types to represent this async action? 【发布时间】:2016-02-11 18:54:42 【问题描述】:

我正在为搜索系统构建一个前端,其中几乎所有用户操作都需要触发相同的异步操作以重新获取搜索结果。例如,如果用户输入一个关键字,那么我们需要获取/api/search?q=foo,如果他们稍后选择一个类别,我们就获取/api/search?q=foo&categoryId=bar。我最初为FETCH_RESULTSSELECT_CATEGORYDESELECT_CATEGORY 等创建了单独的操作类型。我为FETCH_RESULTS 创建了一个异步操作创建器,但其他操作是同步的。我越想,他们最终都需要从后端重新获取结果并根据后端的响应更新应用程序状态。

使用单个异步操作创建器进行任何更改对我来说有意义吗?还是为每个不同的用户操作(选择关键字、类别或过滤器)使用异步操作创建器会更好?

我认为细化操作的优势在于事件更准确地反映了用户所做的事情(例如用户选择了一个类别),而不是必须查看有效负载以找出实际发生的变化,但它们都非常相似。

【问题讨论】:

【参考方案1】:

我同意 Dan Abramov 的观点:如果文本和类别在您的界面中高度耦合,只需触发 FETCH_RESULTS 并将文本和类别作为操作负载。

如果文本输入和类别选择小部件不共享关闭的父组件,则触发包含文本和类别的FETCH_RESULTS 会很复杂(除非将大量道具向下传递...):你然后需要动作粒度。

Saga/流程管理器模式是我发现在需要这种粒度时有用的一种模式。我在这里写了一点:https://***.com/a/33501899/82609

基本上,在 redux 上实现这一点意味着有一种非常特殊的 reducer 可以触发副作用。这个reducer不是纯粹的,但是没有触发React渲染的目的,而是管理组件的协调。

这是我将如何实现您的用例的示例:

function triggerSearchWhenFilterChangesSaga(action,state,dispatch) 
    var newState = searchFiltersReducer(action,state);
    var filtersHaveChanged =  (newState !== state);
    if ( filtersHaveChanged )  
        triggerSearch(newFiltersState,dispatch)
    
    return newState;



function searchFiltersReducer(action,state = text: undefined,categories: []) 
    switch (action.type) 
        case SEARCH_TEXT_CHANGED:
            return Object.assign(, state, text: action.text);
            break;
        case CATEGORY_SELECTED:
            return Object.assign(, state, categories: state.categories.concat(action.category) );
            break;
        case CATEGORY_UNSELECTED:
            return Object.assign(, state, categories: _.without(state.categories,action.category) );
            break;
    
    return state;

请注意,如果您使用任何时间旅行(记录/重播/撤消/重做/其他)调试器,则在重播操作时应始终禁用 saga,因为您不希望在重播期间调度新操作.

编辑:在 Elm 语言(Redux 的灵感来源)中,我们可以通过“减少”效果然后应用它们来执行此类效果。查看该签名:(state, action) -> (state, Effect)

潜艇上还有这个long discussion。

编辑

我以前不知道,但在 Redux 中,动作创建者可以访问状态。所以 Saga 应该解决的大多数问题通常可以在动作创建者中解决(但它会与 UI 状态产生更多不必要的耦合):

function selectCategory(category) 
  return (dispatch, getState) => 
    dispatch(type: "CategorySelected",payload: category);
    dispatch(type: "SearchTriggered",payload: getState().filters);
  

【讨论】:

在 Redux 中,我们要求人们永远不要对 reducer 造成副作用。如果你想要一个副作用,添加一个store.subscribe 监听器并在那里做。 @DanAbramov 我理解你维护这条黄金法则的观点。但实际上 saga 模式需要状态,reducer 是一个很好的抽象来计算事件日志上的状态。基本上,saga 模式可以定义为“可能执行副作用的 reducer”(无论如何它都不依赖于 Redux)。你可以在 Redux 中按照你想要的方式连接它,包括 store.subscribe 这似乎与我确定的使 url 状态与应用程序状态的其余部分保持一致的模式非常相似。操作仅通过 redux-router 直接修改 URL 状态,如果 URL 状态与应用程序状态的其余部分不匹配,store.subscribe 函数会触发异步获取。它不像纯粹的 Redux,但它可以优雅地处理页面加载和更新,所以我认为我们会坚持下去。 也许,Saga 可以实现为 Redux 中间件(这样只有在通过dispatch() 更改状态时才会触发副作用)?【参考方案2】:

我完全同意Nathan’s answer。

我只想补充一点,为了判断动作 A 和 B 是否真的是一个或两个动作,你需要问自己:“如果我改变一些 reducer 对 A 的反应方式,我是否也需要改变它们的方式?对 B 有什么反应?”

当 reducer 代码中的处理程序一起更改时,它们很可能应该是单个操作。当它们的更改可能不会相互影响时,或者如果许多减速器只处理其中一个而不处理另一个,它们可能应该保持分开。

【讨论】:

【参考方案3】:

这当然是只有您根据您对项目的了解才能真正回答的问题。我不认为让动作更细化有任何固有的优势,如果没有,那就不值得付出额外的努力。我会有一个通用的FILTER_CHANGED 事件,而不用担心能够看到具体发生了什么变化——大概这个动作不会很复杂,所以我不会大量调试这个动作。随着过滤器状态变得更加复杂和多样化,分解动作可能更有意义。不过默认情况下,我并没有真正看到太多价值。

【讨论】:

以上是关于我应该使用一种或几种动作类型来表示这个异步动作吗?的主要内容,如果未能解决你的问题,请参考以下文章

Redux 我可以在单独的减速器中使用一种动作类型吗?

用 jest 测试 redux 异步动作创建者

在常量对象中定义所有通量动作类型是一种常见的做法吗?

我的减速机应该处理@@ redux-saga-test-plan / INIT动作吗?

主题动作挂钩一次性功能

Redux-Thunk - 异步动作创建者承诺和链接不起作用