Запуск действий Redux в ответ на переходы маршрута в маршрутизаторе React


Я использую react-router и redux в моем последнем приложении, и я столкнулся с несколькими проблемами, связанными с изменениями состояния, необходимыми на основе текущих параметров url и запросов.

в основном у меня есть компонент, который должен обновлять его состояние каждый раз, когда меняется url. Состояние передается через реквизит redux с декоратором, как так

 @connect(state => ({
   campaigngroups: state.jobresults.campaigngroups,
   error: state.jobresults.error,
   loading: state.jobresults.loading
 }))

на данный момент я использую метод жизненного цикла componentWillReceiveProps для ответа на изменения url, поступающие из реагировать-маршрутизатор с реагировать-маршрутизатор пройдет новый реквизит в обработчике при изменении URL-адреса в этом.реквизит.парамы и это.реквизит.запрос-основная проблема с этим подходом заключается в том, что я запускаю действие в этом методе для обновления состояния, которое затем переходит и передает новые реквизиты компонента, который снова запускает тот же метод жизненного цикла, поэтому в основном создавая бесконечный цикл, в настоящее время я устанавливаю переменную состояния, чтобы остановить это.

  componentWillReceiveProps(nextProps) {
    if (this.state.shouldupdate) {
      let { slug } = nextProps.params;
      let { citizenships, discipline, workright, location } = nextProps.query;
      const params = { slug, discipline, workright, location };
      let filters = this._getFilters(params);
      // set the state accroding to the filters in the url
      this._setState(params);
      // trigger the action to refill the stores
      this.actions.loadCampaignGroups(filters);
    }
  }

есть ли стандартная подход к триггерным действиям базируется на переходах маршрута или я могу иметь состояние хранилища, непосредственно связанное с состоянием компонента, а не передавать его через реквизит? Я попытался использовать willTransitionTo статический метод, но у меня нет доступа к этому.реквизит.отправь туда.

2 52

2 ответа:

хорошо, в конце концов я нашел ответ на странице GitHub redux, поэтому опубликую его здесь. Надеюсь, это спасет кого-то от боли.

@deowk есть две части этой проблемы, я бы сказал. Во - первых, componentWillReceiveProps() не является идеальным способом реагирования на изменения состояния-в основном потому, что он заставляет вас думать императивно, а не реактивно, как мы делаем с Redux. Решение состоит в том, чтобы хранить текущую информацию о маршрутизаторе (местоположение, параметры, запрос) внутри вашего магазина. Тогда все ваше состояние находится в том же месте, и вы можете подписаться на него, используя тот же API Redux, что и остальные ваши данные.

хитрость состоит в том, чтобы создать тип действия, который срабатывает при каждом изменении местоположения маршрутизатора. Это легко в предстоящей версии 1.0 React Router:

// routeLocationDidUpdate() is an action creator
// Only call it from here, nowhere else
BrowserHistory.listen(location => dispatch(routeLocationDidUpdate(location)));

теперь состояние вашего магазина всегда будет синхронизировано с состоянием маршрутизатора. Это устраняет необходимость вручную реагировать на изменения параметров запроса и setState () в вашем компоненте выше-просто используйте Redux Соединитель.

<Connector select={state => ({ filter: getFilters(store.router.params) })} />

вторая часть проблемы заключается в том, что вам нужен способ реагировать на изменения состояния Redux за пределами слоя представления, например, запускать действие в ответ на изменение маршрута. Вы можете продолжать использовать componentWillReceiveProps для простых случаев, таких как тот, который вы описываете, если хотите.

для чего-то более сложного, хотя, я рекомендую использовать RxJS, если вы открыты для него. Это именно то, для чего предназначены наблюдаемые-реактивные данные поток.

чтобы сделать это в Redux, сначала создайте наблюдаемую последовательность состояний хранилища. Вы можете сделать это с помощью observableFromStore () rx.

РЕДАКТИРОВАТЬ ПО ПРЕДЛОЖЕНИЮ CNP

import { Observable } from 'rx'

function observableFromStore(store) {
  return Observable.create(observer =>
    store.subscribe(() => observer.onNext(store.getState()))
  )
}

тогда это просто вопрос использования наблюдаемых операторов для подписки на определенные изменения состояния. Вот пример переориентации со страницы входа после успешного входа в систему:

const didLogin$ = state$
  .distinctUntilChanged(state => !state.loggedIn && state.router.path === '/login')
  .filter(state => state.loggedIn && state.router.path === '/login');

didLogin$.subscribe({
   router.transitionTo('/success');
});

эта реализация намного проще, чем та же функциональность с использованием императивных шаблонов, таких как componentDidReceiveProps().

Как упоминалось ранее, решение состоит из двух частей:

1) свяжите информацию о маршруте с состоянием

для этого все, что вам нужно сделать, это установить react-router-redux. Следуйте инструкциям, и все будет хорошо.

после того как все установлено, вы должны иметь routing государство, как это:

state

2) наблюдайте за изменениями маршрута и запускайте свои действия

где-то в коде вы должны есть что-то вроде этого сейчас:

// find this piece of code
export default function configureStore(initialState) {
    // the logic for configuring your store goes here
    let store = createStore(...);
    // we need to bind the observer to the store <<here>>
}

что вы хотите сделать, это наблюдать за изменениями в магазине, так что вы можете dispatch действия, когда что-то меняется.

как упоминал @deowk, вы можете использовать rx, или вы можете написать свой собственный наблюдатель:

reduxStoreObserver.js

var currentValue;
/**
 * Observes changes in the Redux store and calls onChange when the state changes
 * @param store The Redux store
 * @param selector A function that should return what you are observing. Example: (state) => state.routing.locationBeforeTransitions;
 * @param onChange A function called when the observable state changed. Params are store, previousValue and currentValue
 */
export default function observe(store, selector, onChange) {
    if (!store) throw Error('\'store\' should be truthy');
    if (!selector) throw Error('\'selector\' should be truthy');
    store.subscribe(() => {
        let previousValue = currentValue;
        try {
            currentValue = selector(store.getState());
        }
        catch(ex) {
            // the selector could not get the value. Maybe because of a null reference. Let's assume undefined
            currentValue = undefined;
        }
        if (previousValue !== currentValue) {
            onChange(store, previousValue, currentValue);
        }
    });
}

теперь, все, что вам нужно сделать, это использовать reduxStoreObserver.js мы просто написали, чтобы наблюдать за изменениями:

import observe from './reduxStoreObserver.js';

export default function configureStore(initialState) {
    // the logic for configuring your store goes here
    let store = createStore(...);

    observe(store,
        //if THIS changes, we the CALLBACK will be called
        state => state.routing.locationBeforeTransitions.search, 
        (store, previousValue, currentValue) => console.log('Some property changed from ', previousValue, 'to', currentValue)
    );
}

приведенный выше код делает нашу функцию быть вызывается каждый раз locationBeforeTransitions.поиск изменений в состоянии (в результате навигации пользователя). Если вы хотите, вы можете наблюдать строку запроса que и так далее.

если вы хотите вызвать действие в результате изменения маршрута, все, что вам нужно сделать, это store.dispatch(yourAction) внутри обработчика.