1.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,触发路径跳转等事件 // import {applyMiddleware,compose,createStore} from 'redux' // import {routerMiddleware} from 'react-router-redux' // import thunk from 'redux-thunk' // createStore(reducer,initialState,// compose( applyMiddleware([thunk,routerMiddleware(history)]) ) // ); export routerMiddleware from './middleware'
2.middleware.js
import { CALL_HISTORY_METHOD } from './actions' // 构建route中间件,用于分发action,触发路径跳转等事件,作为外部的routerMiddleware接口 // 使用方式为 // import {applyMiddleware,routerMiddleware(history)]) ) // ); // // history可以是客户端hashHistroy(url散列变化),服务器端boswerHistroy(路径、查询参数变化) // 通过hashHistroy、boswerHistroy触发期望的事件,即hashHistroy、boswerHistroy方法执行 // method是hashHistroy、boswerHistroy的接口方法,包含push、replace、go、goBack、goForward方法 // 问题:hashHistroy、boswerHistroy不使用redux.store.dispatch方法触发事件,是否会影响state??? // redux-logger插件需要在react-router-redux插件挂载前挂载,方能打印action??? export default function routerMiddleware(history) { return () => next => action => { if (action.type !== CALL_HISTORY_METHOD) { return next(action) } const { payload: { method,args } } = action history[method](...args) } }
3.reducer.js
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 }
4.actions.js
export const CALL_HISTORY_METHOD = '@@router/CALL_HISTORY_METHOD' function updateLocation(method) { return (...args) => ({// 返回actionCreater 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,goForward }
5.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 } = {}) { // 确保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()) // locationState.locationBeforeTransitions为真值时,跳转路由事件未发生 return locationState.locationBeforeTransitions || (useInitialIfEmpty ? initialLocation : undefined) } // 初始化route状态数据 initialLocation = getLocationInStore() // 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 } currentLocation = location if (!initialLocation) { initialLocation = location if (getLocationInStore()) { return } } store.dispatch({ type: LOCATION_CHANGE,payload: location }) } // hashHistory、boswerHistory监听浏览器location变更,触发store.dispatch变更store中路由状态 unsubscribeFromHistory = history.listen(handleLocationChange) // support history 3.x // 初始化更新store中路由状态 if(history.getCurrentLocation) { handleLocationChange(history.getCurrentLocation()) } // The enhanced history uses store as source of truth return { ...history,// store中dispatch方法触发时,绑定执行函数listener,以绑定前的路由状态为参数 listen(listener) { // 绑定前的路由状态 let lastPublishedLocation = getLocationInStore(true) let unsubscribed = false// 确保listener在解绑后不执行 const unsubscribeFromStore = store.subscribe(() => { const currentLocation = getLocationInStore(true) if (currentLocation === lastPublishedLocation) { return } lastPublishedLocation = currentLocation if (!unsubscribed) { listener(lastPublishedLocation) } }) listener(lastPublishedLocation) return () => { unsubscribed = true unsubscribeFromStore() } },// 解绑函数,包括location到store的handleLocationChange、store到location的handleStoreChange unsubscribe() { if (adjustUrlOnReplay) { unsubscribeFromStore() } unsubscribeFromHistory() } } }