Part01 What’s the problem
这段代码企图是把router通报props的路由信息再通报给redux。有这么几个题目:
- 假如靠组件生命周期转发 每一个路由下面的顶级组件都要调如许一个action
- 而且,假如路由有参数转变(许多时刻页面状况的参数会在路由中表现),这段代码是没法检测的,还须要在componentWillReceiveProps里去处置惩罚逻辑。
- 另有这个setTimeout处理异步题目,极端不文雅。
Can’t cooperate
redux 是状况治理的库,router 是(唯一)掌握页面跳转的库。二者都很优美,然则不优美的是二者没法协同事情。换句话说,当路由变化今后,store 没法感知到。
redux是想把绝大多数运用程序的状况都保存在单一的store里,而当前的路由状况显著是运用程序状况很重要的一部分,应当是要保存在store中的。
如今是,假如直接运用react router,就意味着一切路由相干的信息脱离了Redux store的掌握,假借组件接收router信息转发dispatch的要领属于反形式,违犯了redux的设想头脑,也给我们运用程序带来了更多的不确定性。
Part02 What do we need
我们须要一个如许的路由体系,他妙技运用React Router的声明式特征,又能将路由信息整合进Redux Store中。
react-router-redux
react-router-redux 是 redux 的一个中间件(中间件:JavaScript 代办形式的另一种实践 针对 dispatch 完成了要领的代办,在 dispatch action 的时刻增添或许修正) ,重要作用是:
加强了React Router库中history这个实例,以许可将history中接收到的变化反应到state中去。
Part03 How to use
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux'
import { Router, Route, browserHistory } from 'react-router'
import { syncHistoryWithStore, routerReducer } from 'react-router-redux'
import reducers from '<project-path>/reducers'
const store = createStore(
combineReducers({
...reducers,
routing: routerReducer
})
)
const history = syncHistoryWithStore(browserHistory, store)
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App} />
</Router>
</Provider>,
document.getElementById(‘app')
)
运用简朴直白的api syncHistoryWithStore来完成redux的绑定事情,我们只须要传入react router中的history(前面提到的)以及redux中的store,就能够取得一个加强后的history对象。
将这个history对象传给react router中的Router组件作为props,就给运用供应了视察路由变化并转变store的才能。
如今,只需您按下浏览器按钮或在运用程序代码中导航,导航就会起首经由过程Redux存储区通报新位置,然后再通报到React Router以更新组件树。假如您计时游览,它还会将新状况通报给React Router以再次更新组件树。
怎样接见容器组件中的路由器状况?
React Router 经由过程途径组件的props供应路由信息。这使得从容器组件接见它们变得轻易。当运用react-redux对connect()你的组件举行陈说时,你能够从第二个参数mapStateToProps接见路由器的道具:
Part04 Code principle
https://github.com/reactjs/react-router-redux
// index.js
/**
* 作为外部 syncHistoryWithStore
* 绑定store.dispatch要领引发的state中路由状况变动到影响浏览器location变动
* 绑定浏览器location变动触发store.dispatch,更新state中路由状况
* 返回当前的histroy、绑定要领listen(dispatch要领触发时实行,以绑定前的路由状况为参数)、解绑函数unsubscribe
*/
export syncHistoryWithStore from './sync'
/**
* routerReducer监听路由变动子reducer,经由过程redux的combineReducers复合多个reducer后运用
*/
export { LOCATION_CHANGE, routerReducer } from './reducer'
/**
* 构建actionCreater,作为外部push、replace、go、goBack、goForward要领的接口,一般不直接运用
*/
export {
CALL_HISTORY_METHOD,
push, replace, go, goBack, goForward,
routerActions
} from './actions'
/**
* 构建route中间件,用于分发action,触发途径跳转等事宜
*/
export routerMiddleware from './middleware'
// sync.js
import { LOCATION_CHANGE } from './reducer'
// 默认用state.routing存取route变动状况数据
const defaultSelectLocationState = state => state.routing
/**
* 作为外部syncHistoryWithStore接口要领
* 绑定store.dispatch要领引发的state中路由状况变动到影响浏览器location变动
* 绑定浏览器location变动触发store.dispatch,更新state中路由状况
* 返回当前的histroy、绑定要领listen(dispatch要领触发时实行,以绑定前的路由状况为参数)、解绑函数unsubscribe
*/
export default function syncHistoryWithStore(history, store, {
// 商定redux.store.state中哪一个属性用于存取route变动状况数据
selectLocationState = defaultSelectLocationState,
// store中路由状况变动是不是引发浏览器location转变
adjustUrlOnReplay = true
} = {}) {
// Ensure that the reducer is mounted on the store and functioning properly.
// 确保redux.store.state中某个属性绑定了route变动状况
if (typeof selectLocationState(store.getState()) === 'undefined') {
throw new Error(
'Expected the routing state to be available either as `state.routing` ' +
'or as the custom expression you can specify as `selectLocationState` ' +
'in the `syncHistoryWithStore()` options. ' +
'Ensure you have added the `routerReducer` to your store\'s ' +
'reducers via `combineReducers` or whatever method you use to isolate ' +
'your reducers.'
)
}
let initialLocation // 初始化route状况数据
let isTimeTraveling // 浏览器页面location.url转变过程当中标识,区分页面链接及react-router-redux变动location两种状况
let unsubscribeFromStore // 移除store.listeners中,因路由状况引发浏览器location变动函数
let unsubscribeFromHistory // 移除location变动引发路由状况更新函数
let currentLocation // 纪录上一个当前路由状况数据
// 猎取路由事宜触发后路由状况,或许useInitialIfEmpty为真值猎取初始化route状况,或许undefined
const getLocationInStore = (useInitialIfEmpty) => {
const locationState = selectLocationState(store.getState())
return locationState.locationBeforeTransitions ||
(useInitialIfEmpty ? initialLocation : undefined)
}
// 初始化route状况数据
initialLocation = getLocationInStore()
// If the store is replayed, update the URL in the browser to match.
// adjustUrlOnReplay为真值时,store数据转变事宜dispatch发生后,浏览器页面更新location
if (adjustUrlOnReplay) {
// 由store中路由状况转变状况,更新浏览器location
const handleStoreChange = () => {
// 猎取路由事宜触发后路由状况,或许初始路由状况
const locationInStore = getLocationInStore(true)
if (currentLocation === locationInStore || initialLocation === locationInStore) {
return
}
// 浏览器页面location.url转变过程当中标识,区分页面链接及react-router-redux变动location两种状况
isTimeTraveling = true
// 纪录上一个当前路由状况数据
currentLocation = locationInStore
// store数据转变后,浏览器页面更新location
history.transitionTo({
...locationInStore,
action: 'PUSH'
})
isTimeTraveling = false
}
// 绑定事宜,完成功能为,dispatch要领触发store中路由状况转变时,更新浏览器location
unsubscribeFromStore = store.subscribe(handleStoreChange)
// 初始化设置路由状况时引发页面location转变
handleStoreChange()
}
// 页面链接变动浏览器location,触发store.dispatch变动store中路由状况
const handleLocationChange = (location) => {
// react-router-redux引发浏览器location变动过程当中,无效;页面链接变动,有用
if (isTimeTraveling) {
return
}
// Remember where we are
currentLocation = location
// Are we being called for the first time?
if (!initialLocation) {
// Remember as a fallback in case state is reset
initialLocation = location
// Respect persisted location, if any
if (getLocationInStore()) {
return
}
}
// Tell the store to update by dispatching an action
store.dispatch({
type: LOCATION_CHANGE,
payload: location
})
}
// hashHistory、boswerHistory监听浏览器location变动,触发store.dispatch变动store中路由状况
unsubscribeFromHistory = history.listen(handleLocationChange)
// History 3.x doesn't call listen synchronously, so fire the initial location change ourselves
// 初始化更新store中路由状况
if (history.getCurrentLocation) {
handleLocationChange(history.getCurrentLocation())
}
// The enhanced history uses store as source of truth
return {
...history,
// store中dispatch要领触发时,绑定实行函数listener,以绑定前的路由状况为参数
listen(listener) {
// Copy of last location.
// 绑定前的路由状况
let lastPublishedLocation = getLocationInStore(true)
// Keep track of whether we unsubscribed, as Redux store
// only applies changes in subscriptions on next dispatch
let unsubscribed = false // 确保listener在解绑后不实行
const unsubscribeFromStore = store.subscribe(() => {
const currentLocation = getLocationInStore(true)
if (currentLocation === lastPublishedLocation) {
return
}
lastPublishedLocation = currentLocation
if (!unsubscribed) {
listener(lastPublishedLocation)
}
})
// History 2.x listeners expect a synchronous call. Make the first call to the
// listener after subscribing to the store, in case the listener causes a
// location change (e.g. when it redirects)
if (!history.getCurrentLocation) {
listener(lastPublishedLocation)
}
// Let user unsubscribe later
return () => {
unsubscribed = true
unsubscribeFromStore()
}
},
// 解绑函数,包含location到store的handleLocationChange、store到location的handleStoreChange
unsubscribe() {
if (adjustUrlOnReplay) {
unsubscribeFromStore()
}
unsubscribeFromHistory()
}
}
}
// reducer.js
/**
* This action type will be dispatched when your history
* receives a location change.
*/
export const LOCATION_CHANGE = '@@router/LOCATION_CHANGE'
const initialState = {
locationBeforeTransitions: null
}
/**
* 监听路由变动子reducer,经由过程redux的combineReducers复合多个reducer后运用,作为外部routerReducer接口
* 提醒redux运用过程当中,可经由过程子组件模块中注入reducer,再运用combineReducers复合多个reducer
* 末了运用replaceReducer要领更新当前store的reducer,意义是构建reducer拆解到各个子模块中
* */
export function routerReducer(state = initialState, { type, payload } = {}) {
if (type === LOCATION_CHANGE) {
return { ...state, locationBeforeTransitions: payload }
}
return state
}
// actions.js
export const CALL_HISTORY_METHOD = '@@router/CALL_HISTORY_METHOD'
function updateLocation(method) {
return (...args) => ({
type: CALL_HISTORY_METHOD, // route事宜标识,防止和用于定义的action争执
payload: { method, args } // method系hashHistroy、boswerHistroy对外接口要领名,args为参数
})
}
/**
* 返回actionCreater,作为外部push、replace、go、goBack、goForward要领的接口,一般不直接运用
*/
export const push = updateLocation('push')
export const replace = updateLocation('replace')
export const go = updateLocation('go')
export const goBack = updateLocation('goBack')
export const goForward = updateLocation('goForward')
export const routerActions = { push, replace, go, goBack, goForward }
结束
(此文由PPT摘抄完成)PPT链接