React全家桶使用總結

最近項目使用react全家桶(react+redux+react-router(v4)+react-router-redux(v5-alpah.6)+react-router-config(v1-beta.4))來開發網站,使用過程當中遇到這樣或者那樣的問題,也在sg上面提了好多問題,如:如何監聽路由切換?componentWillReceiveProps渲染死循環?Route嵌套路由致使問題等等,有興趣的能夠直接打開個人問題看看。vue

如今我來分析和總結一下,如何處理這些問題,以及如何去使用。
首先如何結合redux+react-router+react-router-redux+react-router-config,這也是重點所在,下面代碼已經給出了,並且官網也有例子,你們能夠參考一下,我如今只講些重點,細節我就很少說了,若是有問題能夠加我建的羣進去提問:464696550react

重點1:thunk和react-router-redux中間件的使用ios

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文件以下:es6

reducers/index.jsvue-router

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來監聽,下面是個人具體代碼你們能夠參考下:redux

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帶來的問題,下面就是些具體的代碼:axios

<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組件就是我抽取出來的。react-router

重點4:推薦使用react-router-config來加載路由不要使用jsx方式
這也是官網的react-router-config庫提供的功能,因此感受上更像vue-router了,這樣很方便管理,以下就是個人路由代碼:app

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因此有什麼不規範或者有什麼更好的建議能夠跟我交流討論,我都很是歡迎。

clipboard.png

下面這段代碼就是具體講如何結合幾個框架了,你們能夠看看參考參考,我只列出幾個比較重要的文件和組件代碼。

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;
    }
};
相關文章
相關標籤/搜索