react-router-redux源码

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, compose, createStore} from 'redux'
// 	import {routerMiddleware} from 'react-router-redux'
// 	import thunk from 'redux-thunk'
//  createStore(reducer,initialState,
//  	compose( applyMiddleware([thunk, 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, replace, go, goBack, 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()
        }
    }
}

猜你喜欢

转载自schifred.iteye.com/blog/2348252