Выполнение действий 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 там.
Ответы
Ответ 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)
внутри обработчика.