npm install react-router-dom
容器组件的区别
在开发时我们一般使用HashRouter,上线后我们改用BrowserRouter
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>
)
}
}
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;
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;
}
}
}
我们希望匹配到一个后就停止匹配,不在继续匹配下一个路由,我们可以使用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);
}
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;
}
}
我们想给点击后的菜单增加激活样式,同样依然采用高阶组件的方式进行包装 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;
}
}