路由相关的库还有一个connected-react-router
,这个库做到了以下几点
- 将 router 的数据与 store 同步,并且从 store 访问
- 通过 dispatch actions 导航
- 在 redux devtools 中支持路由改变的时间旅行调试
使用
配置仓库
import { applyMiddleware, compose, createStore } from 'redux'
// import { routerMiddleware } from 'connected-react-router'
import { routerMiddleware } from '../connected-router-self'
import history from './history'
import reducers from './reducers'
console.log(history) // 一个对象,拥有push、go、back、forward listen等方法
export default function configureStore(preloadedState) {
const store = createStore(
reducers, // root reducer with router state
preloadedState,
compose(
applyMiddleware( // appliMiddleware返回一个函数,参数为createStore,返回的函数的作用是将多个中间件串联调用。
routerMiddleware(history), // for dispatching history actions
// ... other middlewares ...
),
),
)
return store
}
生成Router
import React from 'react'
import {Provider} from 'react-redux'
import ReactDOM from 'react-dom'
// import {ConnectedRouter} from 'connected-react-router'
import {ConnectedRouter} from './connected-router-self'
import {Route, Link} from 'react-router-dom'
import configureStore from './store/configureStore'
import history from './store/history'
import Counter from './components/Counter'
import Home from './components/Home'
import './app.css'
const store = configureStore()
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}> { /* place ConnectedRouter under Provider */ }
<> { /* your usual react-router v4/v5 routing */ }
<Link to="/">Home</Link>
<br></br>
<Link to="/counter">Counter</Link>
<Route exact={true} path="/" component={Home} />
<Route path="/counter" component={Counter} />
</>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
)
action creator
{
increment(){
return {type:types.INCREMENT}
},
decrement(){
return {type:types.DECREMENT}
},
go(path){
return push(path)
}
}
在组件中调用action
onClick = () => {
this.props.go('/')
}
实现思路
当组件中dispatch action的时候,进入routerMiddleware
中间件。该中间件的作用是识别路由操作相关action,执行history暴露的方法,比如push,在hash路由中就会执行window.location.hash = to
,进而改变路由。React-Router内部会监听hash变化,重新渲染React组件。
import {CALL_HISTORY_METHOD} from './constant'
export default function routerMiddleware (history) {
return function ({dispatch, getState}) {
return function (next) { // 可能是dispatch,也可能是其他中间件最内层的函数
return function (action) {
// 如果是第一个中间件的最内层,会被applyMiddleware执行后作为洋葱的最外层(第一个执行)
// 如果是最后一个中间件的最内层,会调用next(action)相当于store.dispatch(action)
// 和上面的dispatch不同的是,如果调用上面的dispatch,相当于再次调用了用中间件增强后的dispatch,会重新执行一遍
// 所有中间件。上面的dispatch常用于redux-thunk和redux-promise对异步action的处理。
if (action.type !== CALL_HISTORY_METHOD) {
return next(action)
}
const {method, path} = action.payload
history[method](path)
}
}
}
}
ConnectedRouter
是一个组件,它的作用是在组件渲染完成后监听hash变化,然后将路由数据以及变更的形式存到redux 仓库中。
import React, { Component } from 'react'
import { Router } from 'react-router'
import { LOCATION_CHANGE } from './constant'
import { ReactReduxContext } from 'react-redux'
export default class ConnectedRouter extends Component {
static contextType = ReactReduxContext
componentDidMount() {
// listen的回调函数会在hash变化的时候执行
this.unlisten = this.props.history.listen((location, action) => {
this.context.store.dispatch({
type: LOCATION_CHANGE,
payload: {
location,
action
}
})
})
}
componentWillUnmount() {
this.unlisten()
}
render() {
const { history, children } = this.props
return (
<Router history={history}>
{children}
</Router>
)
}
}
connectRouter
是一个函数,返回一个函数,该函数作为redux仓库中路由数据的reducer,进行更新仓库操作。
import {LOCATION_CHANGE} from './constant'
export default function connectRouter (history) {
let initState = {
location: history.location,
action: history.action
}
return function (state=initState, action) {
if (action.type === LOCATION_CHANGE) {
return {
location: action.payload.location,
action: action.payload.action
}
}
return state
}
}