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

前端 未结 3 1569

I\'m building a front-end for a search system where almost all user actions need to trigger the same async action to re-fetch search results. For example, if a user enters a key

3条回答
  •  无人共我
    2021-02-13 21:23

    I agree with Dan Abramov: if the text and categories are highly coupled in your interface, just fire FETCH_RESULTS with the text and categories as action payload.

    If the text input and categories selection widget do not share a close parent component, it is complicated to fire a FETCH_RESULTS which contains the text and categories (unless passing a lot of props down the tree...): you then need the action granularity.

    One pattern that I have found helpful when such granularity is needed is the Saga / Process manager pattern. I've written a bit about it here: https://stackoverflow.com/a/33501899/82609

    Basically, implementing this on redux would mean there's a very special kind of reducer that can trigger side-effects. This reducer is not pure, but do not have the purpose of triggering React renderings, but instead manage coordination of components.

    Here's an example of how I would implement your usecase:

    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;
    }
    

    Note if you use any time-traveling (record/replay/undo/redo/whatever) debugger, the saga should always be disabled when replaying actions because you don't want new actions to be dispatched during the replay.

    EDIT: in Elm language (from which Redux is inspired) we can perform such effects by "reducing" the effects, and then applying them. See that signature: (state, action) -> (state, Effect)

    There is also this long discussion on the subjet.

    EDIT:

    I did not know before but in Redux action creators can access state. So most problems a Saga is supposed to resolve can often be solved in the action creators (but it creates more unnecessary coupling to UI state):

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

提交回复
热议问题