响应在React路由器中的路由转换来触发Redux操作
我在我最新的应用程序中使用react-router和redux,我正在面对一些与根据当前的url参数和查询所需的状态更改相关的问题。
基本上我有一个组件需要更新它的状态,每次URL的变化。 国家正在通过像装饰这样的装饰者通过道具传入
@connect(state => ({ campaigngroups: state.jobresults.campaigngroups, error: state.jobresults.error, loading: state.jobresults.loading }))
目前,我正在使用componentWillReceiveProps生命周期方法来响应来自react-router的url更改,因为react-router会在URL中的this.props.params和this.props.query发生更改时将新的props传递给处理程序。这个方法的主要问题是我在这个方法中触发一个动作来更新状态 – 然后传递新的道具组件,这将再次触发相同的生命周期方法 – 所以基本上创build一个无限循环,目前我正在设置一个状态variables来阻止这种情况的发生。
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那里。
好的,我最终在redux的github页面上find了一个答案,所以会在这里发布。 希望它可以节省一些人的痛苦。
@deowk这个问题有两个部分,我会说。 第一个是componentWillReceiveProps()不是响应状态变化的理想方式 – 主要是因为它迫使你强制性地进行思考,而不是像Redux那样的被动方式。 解决scheme是将您当前的路由器信息(位置,参数,查询)存储在您的商店内。 然后,所有状态都在同一个地方,您可以使用与其他数据相同的Redux API来订阅它。
诀窍是创build一个动作types,只要路由器位置发生变化就会触发。 在即将到来的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。 这正是可观察的devise – 反应式数据stream。
要在Redux中执行此操作,请首先创build一个可观察的存储状态序列。 你可以使用rx的observableFromStore()来做到这一点。
编辑由CNP提议
import { Observable } from 'rx' function observableFromStore(store) { return Observable.create(observer => store.subscribe(() => observer.onNext(store.getState())) ) }
那么这只是使用可观察运算符来订阅特定状态更改的问题。 以下是成功login后从login页面重新定向的示例:
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()这样的命令式模式的相同function简单得多。
如前所述,解决scheme有两个部分:
1)将路由信息链接到状态
为此,你所要做的就是设置react-router-redux 。 按照说明,你会没事的。
一切都设置好后,你应该有一个routing
状态,像这样:
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.search状态改变(由于用户导航)时被调用。 如果你愿意,你可以观察到查询string等等。
如果您想通过路由更改来触发某个操作,则store.dispatch(yourAction)
在处理程序中store.dispatch(yourAction)
。