Redux: запуск действий в ответ на переходы маршрута в response-router

Созданный на 7 июл. 2015  ·  26Комментарии  ·  Источник: reduxjs/redux

Привет, ребята,

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

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

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

В настоящий момент я использую метод жизненного цикла componentWillReceiveProps для ответа на изменения URL-адреса, поступающие от response-router, поскольку response-router будет передавать новые реквизиты обработчику, когда 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);
    }
  }

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

docs ecosystem question

Самый полезный комментарий

@deowk Я бы сказал, что у этой проблемы две части. Первое , что componentWillReceiveProps() не является идеальным способом для реагирования на изменения состояния - в основном потому , что заставляет вас думать , императивно, а не реактивно , как мы делаем с 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, сначала создайте наблюдаемую последовательность состояний хранилища. Вы можете сделать это с помощью команды redux-rx observableFromStore() .

import { observableFromStore } from 'redux-rx';
const state$ = observableFromStore(store);

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

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() .

Надеюсь, это поможет!

Все 26 Комментарий

@deowk Вы можете сравнить this.props с nextProps чтобы увидеть, изменились ли соответствующие данные. Если он не изменился, вам не нужно снова запускать действие и вы можете выйти из бесконечного цикла.

@johanneslumpe : В моем случае группы кампаний - это довольно большая коллекция объектов, кажется неэффективным использование глубокого сравнения объектов в качестве условия таким образом?

@deowk, если вы используете неизменяемые данные, вы можете просто сравнить ссылки

@deowk Я бы сказал, что у этой проблемы две части. Первое , что componentWillReceiveProps() не является идеальным способом для реагирования на изменения состояния - в основном потому , что заставляет вас думать , императивно, а не реактивно , как мы делаем с 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, сначала создайте наблюдаемую последовательность состояний хранилища. Вы можете сделать это с помощью команды redux-rx observableFromStore() .

import { observableFromStore } from 'redux-rx';
const state$ = observableFromStore(store);

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

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() .

Надеюсь, это поможет!

Нам это нужно в новых документах. Так хорошо написано.

@acdlite : Большое спасибо, ваш совет мне очень помог, хотя я борюсь со следующим:> изменение состояния в магазине путем запуска создателя действия в ответ на изменение URL-адреса.

Я хочу вызвать создателя действия в статическом методе willTransitionTo, поскольку это кажется единственным вариантом в response-router v0.13.3

class Results extends Component  {
..........
}

Results.willTransitionTo = function (transition, params, query) {
 // how do I get a reference to dispatch here?
};

@deowk Я предполагаю, что вы вызываете dispatch непосредственно в экземпляре redux:

const redux = createRedux(stores);
BrowserHistory.listen(location => redux.dispatch(routeLocationDidUpdate(location)));

@deowk В настоящее время я отправляю свои изменения URL в реагирующем маршрутизаторе 0.13.3 следующим образом:

Router.run(routes, Router.HistoryLocation, function(Handler, locationState) {
   dispatch(routeLocationDidUpdate(locationState))
   React.render(<Handler/>, appEl);
});

@acdlite действительно хорошо объяснил! : +1:
Согласитесь, это должно быть и в новых документах :)

Как примечание, BrowserHistory.history() изменилось на BrowserHistory.addChangeListener() .

https://github.com/rackt/react-router/blob/master/modules/BrowserHistory.js#L57

РЕДАКТИРОВАТЬ:

Что может выглядеть так:

history.addChangeListener(() => store.dispatch(routeLocationDidUpdate(location)));

А как насчет начального состояния этого редуктора, @pburtchaell?

У меня следующая установка:

// client.js
class App extends Component {
  constructor(props) {
    super(props);
    this.history = new HashHistory();
  }

  render() {
    return (
      <Provider store={store}>
         {renderRoutes.bind(null, this.history)}
      </Provider>
    );
  }
}

// routes.js
export default function renderRoutes(history) {

  // When the route changes, dispatch that information to the store.
  history.addChangeListener(() => store.dispatch(routeLocationDidUpdate(location)));

  return (
    <Router history={history}>
      <Route component={myAppView}>
        <Route path="/" component={myHomeView}  />
      </Route>
    </Router>
  );
};

Это запускает создателя действия routeLocationDidUpdate() как при каждом изменении маршрута, так и при начальной загрузке приложения.

@pburtchaell можешь поделиться своими routeLocationDidUpdate ?

@gyzerok Конечно. Это создатель действий.

// actions/location.js
export function routeLocationDidUpdate(location) {
  return {
    type: 'LOCATION_UPDATE',
    payload: {
      ...location,
    },
  };
}

@pburtchaell, поэтому в вашем примере я не понимаю, где вы берете данные, необходимые для определенного маршрута

Вот более полная версия моего примера выше:

https://github.com/acdlite/redux-react-router/blob/master/README.md#bonus -reacting-to-state-changes-with-redux-rx

Я предполагаю, что @pburtchaell будет использовать асинхронную версию этого кода для получения данных (из вызова REST и т. Д.). Я не уверен в том, что тогда? Я предполагаю, что затем он переходит в магазин, но будет ли этот магазин местоположением, которое я тогда

<strong i="7">@connect</strong>

в, скажем, в

<Comments />

который уже был бы «подключен» (подписан) на commentStore? Или просто добавить прописать эти действия по местоположению в комментариях Store?

URL как

/comments/123

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

Я также борюсь с этим, выясняя, как вызвать мой статический метод fetchData(dispatch, params) (статический, потому что сервер вызывает его для отправки асинхронных действий FETCH перед начальным рендерингом).

Поскольку ссылка на dispatch существует в контексте, я думаю, что самый чистый способ получить это для вызова fetchData на стороне клиента - это Connector -подобный компонент, который вызывает fetchData или shouldFetchData , либо обратный вызов select получит ссылку на dispatch вместе с state , чтобы мы могли проверить текущую состояние store и отправка действий FETCH мере необходимости.

Последнее более лаконично, но нарушает тот факт, что select должна оставаться чистой функцией. Я изучу первое.

Мое решение таково, хотя оно полностью адаптировано к моей настройке (статический метод fetchData(dispatch, state) который отправляет async _FETCH actions), он будет вызывать любые переданные ему обратные вызовы с соответствующими свойствами: https: // gist.github.com/grrowl/6cca2162e468891d8128 -

Нам обязательно нужно добавить документы! Возможно в разделе «Использование с реактивным роутером».

У нас будет официальный способ сделать это после выпуска 1.0.

Приятно слышать @gaearon.

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

У меня есть наблюдаемый поток, настроенный для синхронизации моего магазина с любыми изменениями маршрута, и у меня есть настройка потока, чтобы смотреть мой магазин. Меня интересует переход на новый маршрут, когда значение изменяется в моем магазине, в частности, когда оно изменяется с undefined на допустимую строку в результате успешной отправки формы в другом месте моего приложения.

Два потока настроены и работают, и хранилище обновляется, но я новичок в Rx, и у меня проблемы с наблюдаемым запросом ... какие-либо указатели? Я на правильном пути? Уверен, что это как-то связано с distinctUntilChanged но сейчас это моя лапша, любая помощь приветствуется :)

РЕДАКТИРОВАТЬ: Ack! Извините, ответил на свой вопрос. Псевдокод ниже. Дай мне знать, если это выглядит ужасно?

const didChangeProject$ = state$
  .distinctUntilChanged(state => state.project._id)
  .filter(state => typeof state.project._id !== 'undefined');

Закрытие в пользу №177. Когда мы перейдем к документированию маршрутизации, нам нужно будет охватить все сценарии: перенаправление при изменении состояния, перенаправление при обратном вызове запроса и т. Д.

Я знаю, что это закрыто ... но с React 0.14 и различными зависимостями 1.0 в бета-версии прямо сейчас, есть ли обновление для этого, а если нет, может ли быть учебник по новым возможностям в React 0.14, response-router, redux и т. Д. написано? Кажется, многие из нас пытаются быть в курсе предстоящих изменений, одновременно изучая / понимая все это. Я знаю, что все в движении (каламбур .. э-э .. не предназначено), но мне кажется, что я вижу фрагменты из разных примеров, которые плохо сочетаются друг с другом, и через несколько дней я все еще не могу заставить мое обновленное приложение работать с маршрутизацией. Скорее всего, это моя собственная вина, поскольку я все еще немного борюсь с пониманием того, как все это работает, просто надеюсь, что скоро выйдет обновленное руководство с последними материалами.

Пока не будет учебника, не стесняйтесь смотреть на examples/real-world котором есть маршрутизация. На данный момент он не является «последним и лучшим», но работает нормально.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги