创建项目
npx create-react-app 6.connected-react-router
cd 6.connected-react-router
cnpm i -S react-router-dom redux react-redux connected-react-router
简单示例
使用步骤
- 1、引入 Router 容器
import {ConnectedRouter} from 'connected-react-router'
- 2、引入 中间件
import {routerMiddleware} from 'connected-react-router';
- 3、引入 reducer
import {connectRouter} from 'connected-react-router';
- 4、push
import {push} from 'connected-react-router';
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Route, Link} from 'react-router-dom';
import {Provider} from 'react-redux';
// ConnectedRouter 类似 HashRouter BrowserRouter
// 它可以实现监听路径变化的功能,当路径发生变化后,会派发特定的action
import {ConnectedRouter} from 'connected-react-router';
import history from './history';
import store from './store';
import Home from './components/Home';
import Counter from './components/Counter';
function App(){
return (
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<ul>
<li><Link to="/" exact>首页</Link></li>
<li><Link to="/counter">计数器</Link></li>
</ul>
<Route path="/" exact component={Home} />
<Route path="/counter" component={Counter} />
</div>
</ConnectedRouter>
</Provider>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
src/history.js
import {createBrowserHistory} from 'history'
let history = createBrowserHistory();
export default history;
src/store/index.js
import {createStore, applyMiddleware} from 'redux';
import history from '../history';
import {routerMiddleware} from 'connected-react-router';
import rootReducer from './reducers';
const store = applyMiddleware(routerMiddleware(history))(createStore)(rootReducer);
export default store;
/**
* routerMiddleware 作用是拦截跳转路径的 action,进行路径跳转
* store.dispatch 会判断你是否要跳转路径,如果是的话,用history.push来跳,如果不是的话,next
*/
reducers
src/store/reducers/index.js
import {combineReducers} from 'redux';
// 这个reducer可以识别ConnectedRouter派发的action,把这个action里面对应的路径信息存入store里
// store.getState().router.location
import {connectRouter} from 'connected-react-router';
import history from '../../history';
import counter from './counter';
const reducers = {
counter,
router: connectRouter(history),
}
const rootReducer = combineReducers(reducers);
export default rootReducer;
src/store/reducers/counter.js
import * as types from '../action-types';
function counter(state={number:0}, action){
switch (action.type) {
case types.ADD:
return {number: state.number+1}
case types.MINUS:
return {number: state.number-1}
default:
return state;
}
}
export default counter;
actions
src/store/action-types.js
export const ADD = 'ADD';
export const MINUS = 'MINUS';
src/store/actions/counter.js
import * as types from '../action-types';
import {push} from 'connected-react-router'; //push是一个方法,会返回一个action
const actions = {
add(){
return {type: types.ADD};
},
minus(){
return {type: types.MINUS};
},
go(path){
return push(path);
},
}
export default actions;
components
src/components/Home.js
import React from 'react';
export default class Home extends React.Component {
render(){
return (
<div>
<h1>Home</h1>
<button onClick={() => this.props.history.goBack()}>返回</button>
</div>
)
}
}
src/components/Counter.js
import React from 'react';
import {connect} from 'react-redux';
import actions from '../store/actions/counter'
class Counter extends React.Component {
render(){
let ps = this.props;
return (
<div>
<p>{ps.number}</p>
<button onClick={ps.add}>+</button>
<button onClick={ps.minus}>-</button>
<button onClick={() => ps.go('/')}>跳转到home</button>
</div>
)
}
}
export default connect(state => state.counter, actions)(Counter)
源码实现
connected-react-router/index.js
export {default as ConnectedRouter} from './ConnectedRouter';
export {default as routerMiddleware} from './routerMiddleware';
export {default as connectRouter} from './connectRouter';
export {push, replace, go, goBack, goForward} from './actions';
export {CALL_HISTORY_METHOD, LOCATION_CHANGE} from './action-types';
connected-react-router/action-types.js
// 调用历史对象上的方法
export const CALL_HISTORY_METHOD = 'CALL_HISTORY_METHOD';
// 向仓库派发动作,请求修改路径信息
export const LOCATION_CHANGE = 'LOCATION_CHANGE';
connected-react-router/actions.js
import * as types from './action-types';
// 路径变化的 actionCreator
export function onLocationChanged (location, action){
return {
type: types.LOCATION_CHANGE,
payload: {
location,
action,
}
}
}
// 跳转路径的actionCreator
function updateLocation(method){
// props.history[method](...args)
return (...args) => ({
type: types.CALL_HISTORY_METHOD, // 调用历史对象中的方法
payload: { //携带额外的数据
method,
args,
}
})
}
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');
connected-react-router/ConnectedRouter.js
import React from 'react';
import {connect, ReactReduxContext} from 'react-redux';
import {Router} from 'react-router-dom';
import {onLocationChanged } from './actions'
class ConnectedRouter extends React.Component {
componentDidMount(){
let ps = this.props;
// 添加监听,当路径发生变化的时候,会执行回调,并传递最新的location和action
this.unlisten = ps.history.listen((location, action) => {
ps.dispatch(onLocationChanged (location, action));
});
}
componentWillUnmount() {
this.unlisten();
}
render(){
const {history, children} = this.props;
return (
<Router history={history}>
{children}
</Router>
)
}
}
export default connect()(ConnectedRouter);
connected-react-router/routerMiddleware.js
import {CALL_HISTORY_METHOD} from './action-types';
function routerMiddleware(history){
// 返回一个处理跳转的中间件
return store => next => action => {
// 如果不是要跳转路径,直接下一步
if (action.type !== CALL_HISTORY_METHOD){
return next(action);
}
const {payload: {method, args}} = action;
history[method](...args);
}
}
export default routerMiddleware;
connected-react-router/connectRouter.js
import {LOCATION_CHANGE} from './action-types';
function connectRouter(history){
const initialState = {
location: history.location,
action: history.action,
}
return function(state=initialState, action){
if (action.type === LOCATION_CHANGE){
return {...state, ...action.payload};
} else {
return state;
}
}
}
export default connectRouter;