這段代碼意圖是把router傳遞props的路由信息再傳遞給redux。有這麼幾個問題:react
redux 是狀態管理的庫,router 是(惟一)控制頁面跳轉的庫。二者都很美好,可是不美好的是二者沒法協同工做。換句話說,當路由變化之後,store 沒法感知到。git
redux是想把絕大多數應用程序的狀態都保存在單一的store裏,而當前的路由狀態明顯是應用程序狀態很重要的一部分,應當是要保存在store中的。github
目前是,若是直接使用react router,就意味着全部路由相關的信息脫離了Redux store的控制,假借組件接受router信息轉發dispatch的方法屬於反模式,違背了redux的設計思想,也給咱們應用程序帶來了更多的不肯定性。express
咱們須要一個這樣的路由系統,他技能利用React Router的聲明式特性,又能將路由信息整合進Redux Store中。redux
react-router-redux 是 redux 的一箇中間件(中間件:JavaScript 代理模式的另外一種實踐 針對 dispatch 實現了方法的代理,在 dispatch action 的時候增長或者修改) ,主要做用是:
增強了React Router庫中history這個實例,以容許將history中接受到的變化反應到state中去。api
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訪問路由器的道具:react-router
// 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連接app