1. 安装 #

npm install react-router-dom

容器组件的区别

2. 跑通基本路由 #

index.js

import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import {HashRouter as Router,Route} from './react-router-dom';
import App from './components/App';
let Home = (props)=><div>首页</div>
let User = ()=><div>用户管理</div>
let Profile = ()=><div>个人设置</div>
ReactDOM.render(
       <App>
         <Route path="/home" component={Home}/>
         <Route path="/user" component={User}/>
         <Route path="/profile" component={Profile}/>
       </App>,
    document.querySelector('#root')
);

components\App.js

import React,{Component} from 'react';
import {HashRouter as Router,Route,Link} from '../react-router-dom';
export default class App extends Component{
    render(){
        return (
            <Router>
                <div className="container">
                    <nav className="navbar navbar-default">
                        <div className="container-fluid">
                            <div className="navbar-header">
                                <div className="navbar-brand">管理系统</div>
                            </div>
                            <ul className="nav navbar-nav">
                                <li><Link to="/home">首页</Link></li>
                                <li><Link to="/user">用户管理</Link></li>
                                <li><Link to="/profile">个人设置</Link></li>
                            </ul>
                        </div>
                    </nav>
                    <div className="row">
                        <div className="col-md-12">
                            {this.props.children}
                        </div>
                    </div>
                </div>
            </Router>
        )
    }
}

HashRouter.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class HashRouter extends Component {
    static childContextTypes = {
        location: PropTypes.object,
        history: PropTypes.object
    }
    constructor(props) {
        super(props);
        this.state = { location: { pathname: window.location.hash.slice(1) || '/' } };
    }
    getChildContext() {
        return {
            location: this.state.location,
            history: {
                push(path) {
                    window.location.hash = path;
                }
            }
        }
    }
    componentDidMount() {
        window.location.hash = window.location.hash || '/';
        let render = () => {
            this.setState({ location: { pathname: window.location.hash.slice(1) || '/' } });
        }
        window.addEventListener('hashchange', render);
    }
    render() {
        return this.props.children;
    }
}

Route.js

import React,{Component} from 'react';
import PropTypes from 'prop-types';
export default class Route extends Component{
    static contextTypes = {
        location:PropTypes.object
    }
    render(){
        let {path,component:Component} = this.props;
        let {location:{pathname}} = this.context;
        if(path == pathname || pathname.startsWith(path)){
            return <Component location={this.context.location}/>;
        }
        return null;
    }
}

Link.js

import React,{Component} from 'react';
import PropTypes from 'prop-types';
export default class Link extends Component{
    static contextTypes = {
        history:PropTypes.object
    }
    render(){
        return (
            <a onClick={()=>this.context.history.push(this.props.to)}>{this.props.children}</a>
        )
    }
}

3. 实现二级路由 #

import React from 'react';
import { Link, Route } from 'react-router-dom';
//增加用户
class UserAdd extends React.Component {
    render() {
        return <div>UserAdd</div>
    }
}
//用户列表
class UserList extends React.Component {
    render() {
        return <div>UserList</div>
    }
}
class UserDetail extends React.Component {
    render() {
        return <div>UserDetail</div>
    }
}

class User extends React.Component {
    render() {
        return (
            <div className="row">
                <div className="col-md-2">
                    <ul className="nav nav-pills nav-stacked">
                        <li className="list-group-item">
                            <Link to='/user/list' >用户列表</Link>
                        </li>
                        <li className="list-group-item">
                            <Link to='/user/add'  >增加用户</Link>
                        </li>
                    </ul>
                </div>
                <div className="col-md-10">
                    <Route path="/user/add" component={UserAdd} />
                    <Route path="/user/list" component={UserList} />
                </div>
            </div>
        )
    }
}


User.UserAdd = UserAdd;
User.UserList = UserList;
User.UserDetail = UserDetail;

export default User;

4. 路径参数 #

User.js

import React from 'react';
import { Link, Route } from '../react-router-dom';
//增加用户
class UserAdd extends React.Component {
    render() {
        return <div>UserAdd</div>
    }
}
//用户列表
class UserList extends React.Component {
    render() {
        return <div>UserList</div>
    }
}
class UserDetail extends React.Component {
    render() {
        return <div>UserDetail {this.props.match.params.id}</div>
    }
}

class User extends React.Component {
    render() {
        return (
            <div className="row">
                <div className="col-md-2">
                    <ul className="nav nav-pills nav-stacked">
                        <li className="list-group-item">
                            <Link to='/user/list' >用户列表</Link>
                        </li>
                        <li className="list-group-item">
                            <Link to='/user/add'  >增加用户</Link>
                        </li>
                    </ul>
                </div>
                <div className="col-md-10">
                    <Route path="/user/add" component={UserAdd} />
                    <Route path="/user/list" component={UserList} />
                    <Route path="/user/detail/:id" component={UserDetail} />
                </div>
            </div>
        )
    }
}


User.UserAdd = UserAdd;
User.UserList = UserList;
User.UserDetail = UserDetail;

export default User;

Route.js

import React,{Component} from 'react';
import PropTypes from 'prop-types';
import pathToRegexp from 'path-to-regexp';
export default class Route extends Component{
    static contextTypes = {
        location:PropTypes.object
    }
    constructor(props){
        super(props);
        this.path = props.path;
        this.keys = [];
        this.regexp = pathToRegexp(this.path,this.keys,{end:false});
        this.keys = this.keys.map(key=>key.name);
    }
    render(){
        let {path,component:Component} = this.props;
        let {location:{pathname}} = this.context;
        let result = pathname.match(this.regexp);
        let props = {
            location,
            history:this.context.history
        }
        if(result){
            let [url,...values] = result;
            let match = {
                url,
                path,
                params:this.keys.reduce((memo,key,index)=>{
                    memo[key] = values[index];
                    return memo;
                },{})
            }
            props.match = match;
            console.log(match);
            if(Component){
                return <Component {...props}/>
            }
        }else{
            return null;
        }

    }
}

5. Switch #

我们希望匹配到一个后就停止匹配,不在继续匹配下一个路由,我们可以使用Switch组件

import React,{Component} from 'react';
import PropTypes from 'prop-types';
import pathToRegexp from 'path-to-regexp';
export default class Switch extends Component{
    static contextTypes={
        location: PropTypes.object
    }
    render() {
        let children=this.props.children;
        let {pathname}=this.context.location;
        for (let i=0;i<children.length;i++){
            let child=children[i];
            let {path}=child.props;
            if (match(pathname,path)){
                return child;
            }
        }
        return null;
    }
}
function match(pathname,path) {
    return pathToRegexp(path).test(pathname);
}

6. 受保护的路由 #

Protected.js

import React from 'react';
import {Route,Redirect} from '../react-router-dom';
export default ({component:Component,...rest})=>(
    <Route {...rest} render={(props) => (
        localStorage.getItem('logined')? <Component {...props} />:<Redirect to={{pathname:'/login',state:{from:props.location.pathname}}}/>
    )
    }/>
)

Login.js

import React from 'react';
export default class Login extends React.Component{
    handleClick=() => {
        localStorage.setItem('logined',true);
        this.props.history.push(this.props.location.state.from);
    }
    render() {
        return (
            <div>
                <button
                    className="btn btn-primary"
                    onClick={this.handleClick}
                >登陆</button>
            </div>
        )
    }
}

Route.js

import React,{Component} from 'react';
import PropTypes from 'prop-types';
import pathToRegexp from 'path-to-regexp';
export default class Route extends Component{
    static contextTypes = {
        location:PropTypes.object,
        history:PropTypes.object
    }
    constructor(props){
        super(props);
        this.path = props.path;
        this.keys = [];
        this.regexp = pathToRegexp(this.path,this.keys,{end:false});
        this.keys = this.keys.map(key=>key.name);
    }
    render(){
        let {path,component:Component,render} = this.props;
        let {location} = this.context;
        let result = location.pathname.match(this.regexp);
        let props = {
            location,
            history:this.context.history
        }
        if(result){
            let [url,...values] = result;
            let match = {
                url,
                path,
                params:this.keys.reduce((memo,key,index)=>{
                    memo[key] = values[index];
                    return memo;
                },{})
            }
            props.match = match;
            if(Component){
                return <Component {...props}/>
            }else if(render){
                return render(props);
            }
        }else{
            return null;
        }

    }
}

HashRouter.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class HashRouter extends Component {
    static childContextTypes = {
        location: PropTypes.object,
        history: PropTypes.object
    }
    constructor(props) {
        super(props);
        this.state = { location: { pathname: window.location.hash.slice(1) || '/',state:{} } };
    }
    getChildContext() {
        let that = this;
        return {
            location: this.state.location,
            history: {
                push(path) {
                    if (typeof path=='object') {
                        let {pathname,state}=path;
                        that.setState({
                            location: {...that.state.location,state}
                        },() => {
                            console.log('this.state.location.state',that.state.location.state);
                            window.location.hash=pathname;
                        });
                    } else {
                        window.location.hash=path;
                    }
                }
            }
        }
    }
    componentDidMount() {
        window.location.hash = window.location.hash || '/';
        let render = () => {
            this.setState({ location: {...this.state.location,pathname: window.location.hash.slice(1) || '/' } });
        }
        window.addEventListener('hashchange', render);
    }
    render() {
        return this.props.children;
    }
}

7. 自定义菜单 #

我们想给点击后的菜单增加激活样式,同样依然采用高阶组件的方式进行包装 MenuLink

import React from 'react';
import {Route,Link} from './react-router-dom'
import './MenuLink.css'
export default ({to,children})=>{
    return <Route path={to} children={(props) => {
        return <li className={"nav-item "+(props.match?"active":"")}><Link to={to} className="nav-link">{children}</Link></li>
    }}/>
}

这里有个children属性和以前render不同,children无论是否路由匹配到都会执行此函数。而render只要在匹配到后才会执行

Route.js

 render() {
        let self=this;
        let {path,component:Component,render,children}=this.props;
        let {location}=self.context;//得到路径
        let result=location.pathname.match(self.regexp);
        let props={
            location,
            history:this.context.history
        }
        if (result) {
            let [url,...values]=result;
            let match={
                url,
                path,
                params: self.keys.reduce((memo,key,index) => {
                    memo[key.name]=values[index];
                    return memo;
                },{})
            }
            props.match=match;
            if (render) {
                return render(props);
            } else if (Component) {
                return <Component {...props}/>;
 +           } else if (children) {
                return children(props);
            }else {
                return null;
            }

 +       } else if (children) {
            return children(props);
        } else {
            return null;
        }
    }