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

Should I use one or several action types to represent this async action?

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

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

我认为细化操作的优势在于事件可以更准确地反映用户的操作(例如用户 select 编辑了一个类别)而不是必须查看有效负载才能弄清楚实际发生了什么变化,但它们都非常相似。

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

我完全同意

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

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

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

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

当需要这种粒度时,我发现一种有用的模式是 Saga / Process manager pattern. I've written a bit about it here:

基本上,在 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;
}

请注意,如果您使用任何时间旅行 (record/replay/undo/redo/whatever) 调试器,则在重放操作时应始终禁用 saga,因为您不希望在重放期间分派新操作.

编辑:在 Elm 语言(Redux 的灵感来源)中,我们可以通过 "reducing" 效果然后应用它们来执行此类效果。看到那个签名:(state, action) -> (state, Effect)

子机上也有这个long discussion

编辑:

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

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