Подтвердить что ты не робот

Выполнение действий Redux в ответ на переход маршрута в React Router

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

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

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

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

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

Существует ли стандартный подход для запуска базы действий на переходах маршрута ИЛИ могу ли я иметь состояние хранилища, напрямую связанное с состоянием компонента, вместо того, чтобы передавать его через реквизиты? Я попытался использовать staticTransitionTo статический метод, но у меня нет доступа к this.props.dispatch там.

4b9b3361

Ответ 1

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

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

Трюк заключается в создании типа действия, который срабатывает всякий раз, когда изменяется местоположение маршрутизатора. Это легко в следующей версии 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.

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

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

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

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

ИЗМЕНИТЬ КАК ПРЕДЛАГАЕТСЯ 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().

Ответ 2

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

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 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.search изменяется в состоянии (в результате пользовательской навигации). Если вы хотите, вы можете наблюдать строку запроса qu и т.д.

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