最近项目使用react全家桶(react+redux+react-router(v4)+react-router-redux(v5-alpah.6)+react-router-config(v1-beta.4))来开发网站,使用过程中遇到这样或者那样的问题,也在sg上面提了好多问题,如:如何监听路由切换?componentWillReceiveProps渲染死循环?Route嵌套路由导致问题等等,有兴趣的可以直接打开我的问题看看。
现在我来分析和总结一下,如何处理这些问题,以及如何去使用。
首先如何结合redux+react-router+react-router-redux+react-router-config,这也是重点所在,下面代码已经给出了,而且官网也有例子,大家可以参考一下,我现在只讲些重点,细节我就不多说了,如果有问题可以加我建的群进去提问:464696550
重点1:thunk和react-router-redux中间件的使用
const middleWares = [thunkMiddleware, historyRouterMiddleware];
let store = createStore(combineReducers({
...reducers,
router: routerReducer
}), applyMiddleware(...middleWares));
没错,你可以使用es6语法...middleWares这样写在applyMiddleware函数参数里面,还有因为routerReducer也会加载到store里面,所以你可以这样使用在createStore里面添加combineReducers把你的相关reducers和router:routerReducer结合加进去,特别注意的是:在reducers文件里面就不要用combineReducer否则会报错,像我的reducers文件如下:
reducers/index.js
import user from './user'
import layout from './layout'
import historyRouter from './historyRouter'
const reducers = {
user,
layout,
historyRouter
};
export default reducers
重点2:如何监听路由变化
这也是我在使用过程中遇到最大的坑,百度了好多也sg上面提问大家都说是通过componentWillReceiveProps事件来监听,而且要放在根路由组件上面,后来在react-router-config上面发现也是通过componentWillReceiveProps来监听,下面是我的具体代码大家可以参考下:
componentWillReceiveProps(nextProps) {
const navigated = nextProps.location !== this.props.previousLocation;
const { location } = this.props;
if (navigated) {
this.props.dispatch(setPreviousLocation(location));
this.routerChanged(location.pathname);
}
}
重点3:Route路由不要用嵌套的方式
一开始我对react-router不熟悉感觉就是没有vue-router用得方便,而且它是通过jsx的形式来呈现的,所以我一开始在页面布局上是这样做的,首先使用Route来定义一个路由,然后该路由组件里面我再定义多个Route,可是这样会发现有很多问题,尤其是“/”路由,所以我在页面布局的时候把公共样式 抽取出来做成一个单独组件来使用,这样就防止Route嵌套Route带来的问题,下面就是些具体的代码:
<Route exact path="/" component={Index}/>
<Route exact path="/explore" component={Explore}/>
<Route exact path="/user" component={User}/>
<Route exact path="/mysites" component={MySites}/>
值得注意的是上面的Index、Explore、User、和MySites这4个组件里面会用到Layout组件,这个Layout组件就是我抽取出来的。
重点4:推荐使用react-router-config来加载路由不要使用jsx方式
这也是官网的react-router-config库提供的功能,所以感觉上更像vue-router了,这样很方便管理,如下就是我的路由代码:
router/index.js
import Login from '../containers/Login'
import Register from '../containers/Register'
import VerifyEmail from '../containers/VerifyEmail'
import NoMatch from '../containers/NoMatch'
import Explore from '../containers/Explore'
import MySites from '../containers/MySites'
import User from '../containers/User'
import Index from '../containers/Index'
const routes = [
{
path: '/',
component: Index,
exact: true,
},
{
path: '/explore',
exact: true,
component: Explore
},
{
path: '/user',
exact: true,
component: User
},
{
path: '/mysites',
exact: true,
component: MySites
},
{
path: '/account/register',
exact: true,
component: Register
},
{
path: '/account/login',
exact: true,
component: Login
},
{
path: '/email/verify/:token',
exact: true,
component: VerifyEmail
},
{
component: NoMatch
},
];
export default routes;
这是我项目的目录结构,非常典型的结构没有什么特别的地方,因为还在不断学习react所以有什么不规范或者有什么更好的建议可以跟我交流讨论,我都非常欢迎。
下面这段代码就是具体讲如何结合几个框架了,大家可以看看参考参考,我只列出几个比较重要的文件和组件代码。
app.js
import React from 'react'
import {createStore, combineReducers, applyMiddleware} from 'redux'
import {Provider} from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import createHistory from 'history/createBrowserHistory'
import {ConnectedRouter, routerReducer, routerMiddleware, push} from 'react-router-redux'
import {matchRoutes, renderRoutes} from 'react-router-config'
import {Switch} from 'react-router-dom'
import reducers from './reducers'
import routes from './router'
const history = createHistory();
const historyRouterMiddleware = routerMiddleware(history);
const middleWares = [thunkMiddleware, historyRouterMiddleware];
let store = createStore(combineReducers({
...reducers,
router: routerReducer
}), applyMiddleware(...middleWares));
const App = () => (
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
{renderRoutes(routes)}
</Switch>
</ConnectedRouter>
</Provider>
);
export default App
components/Layout.js
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import classNames from 'classnames';
import Topbar from '../components/layout/Topbar'
import Sidebar from '../components/layout/Sidebar'
import {Route,withRouter} from 'react-router-dom'
import {getUserInfo} from '../utils'
import VerifyEmailDlg from './Dialog/VerifyEmailDlg'
import SitesSidebar from '../components/layout/SitesSidebar'
import {connect} from 'react-redux';
import {setBodyWidth,setIsMySites,setPreviousLocation} from '../actions'
const styles = theme => ({
root: {
width: '100%',
height: '100%',
zIndex: 1,
overflow: 'hidden',
},
appFrame: {
display: 'flex',
width: '100%',
height: '100%',
},
content: {
position: 'absolute',
top: '56px',
right: 0,
left: 0,
bottom: 0,
marginLeft:0,
flexGrow: 1,
boxSizing:'border-box',
backgroundColor: '#fff',
padding:0,
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
height: 'calc(100% - 56px)'
},
contentShift: {
marginLeft: '240px',
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
},
});
class Layout extends React.Component {
constructor(props){
super(props);
this.state = {
showVerifyEmailDig:false,
user:null
};
}
componentWillReceiveProps(nextProps) {
const navigated = nextProps.location !== this.props.previousLocation;
const { location } = this.props;
if (navigated) {
this.props.dispatch(setPreviousLocation(location));
this.routerChanged(location.pathname);
}
}
componentWillMount(){
window.addEventListener('resize', this.resizeListener);
this.resizeListener();
//this.showVerifyEmailDialog();
}
componentWillUnmount(){
window.removeEventListener('resize', this.resizeListener);
}
//监听路由变化
routerChanged(pathname){
if (pathname.indexOf('mysites')!=-1){
this.props.dispatch(setIsMySites(true));
}else{
this.props.dispatch(setIsMySites(false));
}
};
resizeListener=()=> {
let bodyWidth = document.body.offsetWidth;
this.props.dispatch(setBodyWidth(bodyWidth));
};
showVerifyEmailDialog=()=>{
let that = this;
getUserInfo(function(isLogin,user){
if (isLogin&&user.is_active==0){
that.setState({
showVerifyEmailDig:true,
user:user
})
}
});
};
render() {
const { classes,width,showSiteSidebar,sideBarInfo } = this.props;
return (
<div className={classes.root}>
<VerifyEmailDlg
onClose={()=>{
this.setState({
showVerifyEmailDig:false
})
}}
showVerifyEmailDig={this.state.showVerifyEmailDig}
user={this.state.user}/>
<div className={classes.appFrame}>
<Topbar showSearchBar={width>1024}/>
<Sidebar/>
{showSiteSidebar ? <SitesSidebar/>:null}
<main className={classNames(classes.content,((showSiteSidebar||sideBarInfo.open)&&width>1024) && classes.contentShift)}>
{this.props.children}
</main>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
column:state.layout.column,
width:state.layout.bodyWidth,
sideBarInfo:state.layout.sideBarInfo,
showSiteSidebar:state.layout.showSiteSidebar,
isMySites:state.layout.isMySites,
previousLocation:state.historyRouter.previousLocation
}
};
export default withRouter(connect(mapStateToProps)(withStyles(styles)(Layout)));
actions/index.js
import axios from 'axios'
export const LOGIN_INFO = 'LOGIN_INFO';
export const LOGOUT='LOGOUT';
export const SET_COLUMN = 'SET_COLUMN';
export const SET_BODY_WIDTH='SET_BODY_WIDTH';
export const TOGGLE_SIDEBAR='TOGGLE_SIDEBAR';
export const SET_SIDEBAR = 'SET_SIDEBAR';
export const SET_IS_MY_SITES='SET_IS_MY_SITES';
export const SET_PREVIOUS_LOCATION='SET_PREVIOUS_LOCATION';
export function getLoginAction(){
return (dispatch,state)=>{
axios.get('/user/validateUser')
.then((res)=>{
console.log('axios:/user/validateUser');
console.log(res);
dispatch({
type:LOGIN_INFO,
isLogin:res.data.isLogin,
user:res.data.user
})
}).catch((err)=>{
});
}
}
export function logout() {
return (dispatch,state)=>{
axios.get('/user/logout').then((res)=>{
if (res.data.success){
console.log('logout success');
dispatch({
type:LOGOUT,
isLogin:false
})
}
})
}
}
export const setColumn=column=>({
type:SET_COLUMN,
column
});
export const setBodyWidth=width=>({
type:SET_BODY_WIDTH,
width
});
export const toggleSideBar = sideBarInfo=>({
type:TOGGLE_SIDEBAR
});
export const setSidebar = sideBarInfo=>({
type:SET_SIDEBAR,
sideBarInfo
});
export const setIsMySites = isMySites=>({
type:SET_IS_MY_SITES,
isMySites
});
export const setPreviousLocation = location=>({
type:SET_PREVIOUS_LOCATION,
location
});
containers/Index.js
import React from 'react'
import PropTypes from 'prop-types';
import {withStyles} from 'material-ui/styles'
import classNames from 'classnames'
import Grid from 'material-ui/Grid';
import AppBar from 'material-ui/AppBar'
import Tabbar from '../components/Tabbar'
import Layout from '../components/Layout'
import {connect} from 'react-redux';
function TabContainer(props) {
return <div style={{ padding: 20 }}>{props.children}</div>;
}
class Index extends React.Component{
constructor(props){
super(props);
this.state={
tabIndex:0,
}
}
onTabChanged=(index)=>{
this.setState({ tabIndex: index });
};
render(){
const { classes,column } = this.props;
const tabs = [{ id:1,name:'关注者动态' },{ id:2,name:'我的动态' },{ id:3,name:'全部动态' }];
return(
<Layout>
<Grid container spacing={0} justify={'center'}>
<Grid item xs={12}>
<AppBar position="static" color="default" className={classNames(classes.appBar)}>
<Grid container spacing={0} justify={'center'}>
<Grid item xs={column}>
<Tabbar
tabs={tabs}
onTabChanged={this.onTabChanged}
index={this.state.tabIndex} />
</Grid>
</Grid>
</AppBar>
</Grid>
<Grid item xs={column}>
{this.state.tabIndex === 0 && <TabContainer>sdfsf </TabContainer>}
{this.state.tabIndex === 1 && <TabContainer>{'Item Two'}</TabContainer>}
{this.state.tabIndex === 2 && <TabContainer>{'Item Three'}</TabContainer>}
</Grid>
</Grid>
</Layout>
)
}
}
const mapStateToProps = state => {
return {
column:state.layout.column
}
};
const styles = theme =>({
appBar: {
boxShadow:'none !important'
},
});
export default connect(mapStateToProps)(withStyles(styles)(Index))
reducers/layout.js
import {
SET_COLUMN,
SET_BODY_WIDTH,
TOGGLE_SIDEBAR,
SET_SIDEBAR,
SET_IS_MY_SITES
} from '../actions'
let layoutInfo={
column:11,
bodyWidth:0,
isMySites:false,
showSiteSidebar:false,
sideBarInfo:{
open: true,
type:'persistent',//temporary,
}
};
const layout = (state=layoutInfo,action)=>{
switch (action.type){
case SET_COLUMN:
console.log('SET_COLUMN');
return {
...state,
column:action.column
};
case SET_BODY_WIDTH:
console.log('SET_BODY_WIDTH');
let isPersistent = !state.isMySites && action.width>1024;
return{
...state,
bodyWidth:action.width,
column:state.sideBarInfo.open?11:10,
showSiteSidebar:action.width>1024&&state.isMySites,
sideBarInfo:{
open:isPersistent,
type:isPersistent?'persistent':'temporary'
}
};
case TOGGLE_SIDEBAR:
console.log('TOGGLE_SIDEBAR');
let isOpen = !state.sideBarInfo.open;
return{
...state,
column:isOpen?11:10,
sideBarInfo:{
...state.sideBarInfo,
open:isOpen
}
};
case SET_SIDEBAR:
console.log('SET_SIDEBAR');
return{
...state,
sideBarInfo:action.sideBarInfo
};
case SET_IS_MY_SITES:
console.log('SET_IS_MY_SITES');
let showSideBar =state.bodyWidth>1024&&!action.isMySites;
return {
...state,
isMySites:action.isMySites,
showSiteSidebar:state.bodyWidth>1024&&action.isMySites,
sideBarInfo:{
open:showSideBar,
type:showSideBar?'persistent':'temporary'
}
};
default:
return state;
}
};
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。