一、安装路由:
1. 安装
npm install react-router-dom --save
2. 容器组件
安装成功以后,这里我们下载好后需要他内部的路由容器组件,主要包含
- BrowserRouter 浏览器自带的H5 API, restful 风格,需要配合后台;
 - HashRouter 使用 hash 方式进行路由,路径后均有#
 - MemoryRouter 在内存中管理 history ,地址栏不会变化。在 reactNative 中使用。
 
二、基本使用
2.1 跑通路由
我们来声明三个组件 Home , User , Profile 希望访问不同的路径可以实现显示不同的组件
- Home.js
 
import React, { Component } from 'react'export default class Home extends Component {render () {return (<div>HOME</div>)}}
- User.js
 
import React, { Component } from 'react'export default class User extends Component {render () {return (<div>User</div>)}}
- Profile.js
 
import React, { Component } from 'react'export default class Profile extends Component {render () {return (<div>Profile</div>)}}
- index.js
 
import React from 'react';import ReactDOM from 'react-dom';import './index.css';import App from './App';import { HashRouter, Router, Route } from 'react-router-dom'import Home from './components/Home'import User from './components/User'import Profile from './components/Profile'ReactDOM.render(<HashRouter><div><Route path="/home" component={Home} /><Route path="/profile" component={Profile} /><Route path="/user" component={User} /></div></HashRouter>, document.getElementById('root'));
- 这里我们使用了HashRouter,注意 HashRouter 只能包含一个根元素,所以我们在所有的 Route 的外层包了一个 div 标签;
 - Route 组件有 path 和 component 属性,当浏览器地址栏中的 url 和 path 匹配时会显示 component 属性对应的组件;
 
2.2 路由的匹配规则
把浏览器的路由 /home 改为 /home/abc 我们发现,Home 组件依然展示在页面中,这是因为路径开头匹配,就会显示对应的组件;如果有一个组件,希望任何路由都显示,可以把其 path 设置为 /
三、Link 组件
Link 组件也是 react-router-dom 的内置组件,其作用和 VueRouter 的 router-link 类似;点击它可以跳转到指定的路由;
我们写一个 App.js 组件:
- App.js
 
import React from 'react';import logo from './logo.svg';import './App.css';import 'bootstrap/dist/css/bootstrap.css'import { Link } from 'react-router-dom'class App extends React.Component {render () {return (<div><div className="navbar-inverse navbar"><div className="container-fluid"><div className="navbar-header"><div className="navbar-brand">用户管理系统</div><ul className="navbar-nav nav"><li><Link to='/home'>首页</Link></li><li><Link to='/user'>用户管理</Link></li><li><Link to='/profile'>个人中心</Link></li></ul></div></div></div><div className="container"><div className="row"><div className="col-md-12">{this.props.children}</div></div></div></div>)}}export default App;
- index.js
 
注意 HashRouter 是在 App 的外面
import React from 'react';import ReactDOM from 'react-dom';import './index.css';import App from './App';import { HashRouter, Router, Route } from 'react-router-dom'import Home from './components/Home'import User from './components/User'import Profile from './components/Profile'ReactDOM.render(<HashRouter><App><div><Route path="/home" component={Home} /><Route path="/profile" component={Profile} /><Route path="/user" component={User} /></div></App></HashRouter>, document.getElementById('root'));
四、二级路由
很多情况下,在一个导航下还有二级导航,如 /user 下还有 /user/list 和 /user/add ;类似 VueRouter 的嵌套路由;
4.1 实现二级导航
- 就是在某个一级路由中继续嵌套路由,例如在 User.js 中;
 - User.js
 
import React, { Component } from 'react'import { Link, Route } from 'react-router-dom'import UserAdd from './UserAdd'import UserList from './UserList'export default class User extends Component {render () {return (<div><h1>User</h1><div className="row"><div className="col-md-2"><ul className="nav nav-stacked"><li><Link to='/user/list'>用户列表</Link></li><li><Link to='/user/add'>增加用户</Link></li></ul></div><div className="col-md-10"><Route path='/user/list' component={UserList}></Route><Route path='/user/add' component={UserAdd}></Route></div></div></div>)}}
- UserAdd.js
 
import React, { Component } from 'react'export default class UserAdd extends Component {render () {return (<div><h1>UserAdd</h1></div>)}}
- UserList.js
 
import React, { Component } from 'react'export default class UserList extends Component {render () {return (<div><h1>UserList</h1></div>)}}
五、动态路由和路由跳转
进入到添加页,可以实现用户添加,并且可以跳转列表页,列表页要渲染出已经存在的用户列表,页面间的通信方式我们采用 localStorage。并且点击某个用户我们可以进入到详情页中;
5.1 改造 UserAdd.js
import React, { Component } from 'react'export default class UserAdd extends Component {constructor (props) {super()this.state = {name: ''}}submit = () => {let localStr = localStorage.getItem('list')let list = localStr ? JSON.parse(localStr) : []list.push({id: Math.floor(Math.random() * 100),name: this.state.name})localStorage.setItem('list', JSON.stringify(list))this.props.history.push('/user/list')}render () {return (<div><h1>UserAdd</h1><div className="form-group"><label htmlFor="name" className="control-label"></label><input type="text" className="form-control" value={this.state.name} onChange={(e) => this.setState({name: e.target.value})}/></div><div className="form-group"><button className="btn btn-success" onClick={this.submit}>添加</button></div></div>)}}
- 在 添加 按钮中我们通过 props 的 API,this.props.history.push(‘/user/list’) 这就是采用编程式的路由跳转,所有通过路由渲染的的组件都有一些路由的属性;
 
5.2 改造 UserList.js
import React, { Component } from 'react'import { Link } from 'react-router-dom'export default class UserList extends Component {constructor () {super()this.state = {list: []}}componentWillMount () {let listStr = localStorage.getItem('list')let list = listStr ? JSON.parse(listStr) : []if (list.length) this.setState({list})}render () {return (<div><h1>UserList</h1><ul className="list-group">{this.state.list.map((item, index) => {return <li key={index} className='list-group-item'><Link to={`/user/detail/${item.id}`}>id: {item.id}; Username: {item.name}</Link></li>})}</ul></div>)}}
- 这里我们新增加了一个路由,点击用户名的时候还要跳转到详情页;它是一个动态路由,即 /user/detail 后面的部分是不固定的,传递的是参数;
 
5.3 新增 UserDetail.js 用于根据动态路由中的 id 展示对应的用户信息;
- 这里需要注意如何从动态路由中获取到 id,通过 props 上的 match 对象中的 params 对象获取;this.props.match.params 是一个对象,其中包含了当前匹配到动态路由中的参数;
 
import React, { Component } from 'react'export default class UserDetail extends Component {render () {let { id } = this.props.match.paramslet {name } = JSON.parse(localStorage.getItem('list')).find(i => +i.id === +id)return (<div><h1>UserDetail</h1><h1>ID {id};NAME: {name}</h1></div>)}}
六、路由匹配
路由的匹配规则是模糊匹配的,只要前一部分的路由匹配成功就会展示对应的组件,有的时候我们希望匹配有些限制,比如说严格对某个路径进行匹配,或者匹配到某一个路径后就不再匹配了;
在 index.js 中新增加两个路由
ReactDOM.render(<HashRouter><App><div>+ <Route path='/' render={() => <h1>首页</h1>}></Route>+ <Route path='/:name' render={() => <h1>MABIN</h1>}></Route><Route path="/home" component={Home} /><Route path="/profile" component={Profile} /><Route path="/user" component={User} /></div></App></HashRouter>, document.getElementById('root'));
6.1 exact 属性
- 此时我们发现访问 /home 时, 首页、MABIN、Home 组件都显示了,而我们只需往 / 时才显示首页,此时我们需要精确匹配,我们需要在 / 的 Route 上增加 exact 属性;此时,只有访问 / 时才会显示 首页;
 - 此外 Route 组件还接收一个 render 函数,这个 render 可以返回 jsx 元素,当 path 匹配时就会渲染 render 返回的 jsx 元素,可以充当组件的角色;
 
<Route exact path='/' render={() => <h1>首页</h1>}></Route>
6.2 Switch 组件
但是我们发现,现在访问 /home 时,依然有两个路由会被匹配到,我们希望匹配到一个后停止匹配,不再继续匹配下一个路由,我们需要使用 Switch 组件;Switch 组件也是 react-router-dom 的组件,需要导出才能使用;
- 改造 index.js
 
ReactDOM.render(<HashRouter><App><Switch><Route exact path='/' render={() => <h1>首页</h1>}></Route><Route path='/:name' render={() => <h1>MABIN</h1>}></Route><Route path="/home" component={Home} /><Route path="/profile" component={Profile} /><Route path="/user" component={User} /></Switch></App></HashRouter>, document.getElementById('root'));
此时,我们访问 /home 时,只能出现 MABIN 永远不会出现 Home 组件了;
- 注意此时,/:name 是个动态路由,使用 Switch 后,我们发现再切路由无论是 /user 还是 /profile 都会只展示 MABIN,这是因为 Router 认为 /user 和 /profile 也符合 /:name;
 
七、受保护的路由
在真实的项目中,我们会对一些路由进行保护,例如登录后才能访问,类似 VueRouter 的导航守卫;
我们举个例子,我们新增一个登录的路由,我们本地存储一个变量来表示是否登录过了,点击登录按钮将本地变量改为成功状态,此后才能访问用户列表;
当路由切换到 /user 时,我们需要判断是否登录或者是否有权限,如果没登录就需要跳转到登录页,该功能需要依靠高阶组件实现;
7.1 Protected.js
通过高阶组件包装 Route
import React, { Component } from 'react'import { Route, Redirect } from 'react-router-dom'export default ({component: Component, ...others}) => {return <Route {...others} render={(props) => {// props 是 router 对象,包含了当前路由的信息return localStorage.getItem('loginSystem')? <Component {...props} />: <Redirect to={{pathname: '/login', from: props.match.url}} />}}></Route>}
Redirect 组件是用来重定向的,我们新增 from 属性来记录当前匹配的 url ,为了保证登录后可以再跳回到当前匹配的路径
- 增加 Login.js 组件
 
import React, { Component } from 'react'export default class Login extends Component {render () {console.log(this.props.location)return (<div><input type="text" className="form-control"/><button className="btn btn-success" onClick={() => {localStorage.setItem('loginSystem', true);this.props.history.push(this.props.location.from)}}>登录</button></div>)}}
- 修改 index.js
 
import React from 'react';import ReactDOM from 'react-dom';import './index.css';import App from './App';import { HashRouter, Route, Switch } from 'react-router-dom'import Home from './components/Home'import User from './components/User'import Profile from './components/Profile'import Login from './components/Login'import PrivateRoute from './components/Protected'ReactDOM.render(<HashRouter><App><Switch><Route exact path='/' render={() => <h1>首页</h1>}></Route><Route path="/home" component={Home} /><PrivateRoute path="/profile" component={Profile} /><PrivateRoute path="/user" component={User} /><Route path='/login' component={Login} /></Switch></App></HashRouter>, document.getElementById('root'));
- 经过上面的修改后,我们在默认情况下点击 profile 和 user 会被重定向到登录页,当我们登录后会跳转回去,此时就实现了保护路由;
 
八、自定义菜单
通常情况下,我们会给激活的菜单增加选中样式,此时已然需要使用高阶组件进行包装
自定义 MenuLink 组件来替换 Link 组件,并且在其内部我们可以判断是否增加了激活状态;
- MenuLink.js
 
这里有一个
import React, { Component } from 'react'import { Route, Link } from 'react-router-dom'export default ({ to, label }) => {return <Route path={to} children={(props) => {return <li className={props.match ? 'active' : ''}><Link to={to}>{label}</Link></li>}}></Route>}
- 修改 App.js
 
import React from 'react';import logo from './logo.svg';import './App.css';import 'bootstrap/dist/css/bootstrap.css'import {Link} from 'react-router-dom'import MenuLink from './components/MenuLink'class App extends React.Component {render() {return (<div><div className="navbar-inverse navbar"><div className="container-fluid"><div className="navbar-header"><div className="navbar-brand">用户管理系统</div><ul className="navbar-nav nav"><MenuLink to='/home' label='首页'/><MenuLink to='/user' label='用户管理'/><li><Link to='/profile'>个人中心</Link></li></ul></div></div></div><div className="container"><div className="row"><div className="col-md-12">{this.props.children}</div></div></div></div>)}}export default App;
- 在 App.js 中增加选中样式:
 
li.active a {background: red!important;color: red;}
- 此时我们选中首页和用户管理时,都会有对应选中样式;
 
九、Prompt
有的时候,当用户在某个页面进行了一些操作,但是他没有保存就要去另一个页面,我们很有可能要提示一下用户,是否确认跳转;此时我们可以使用 react-router-dom 的 Prompt 组件;
- Prompt 组件在用户要从某个页面离开时,提示用户;
- Prompt 有一个 when 属性,如果为 true 离开时才提示,如果为false不提示
 - Prompt 有一个 message 属性,值是一个函数,接收一个要去往的路由信息对象,返回离开当前页面时的提示信息
 
 
例如我们修改一下新增用户的组件 AddUser.js
- 在 input 的 onChange 事件中判断 input 中的内容不为空表示用户输入过了,此时尚未保存,此时跳走需要提示;而 Prompt 要想弹出,需要把 when 属性绑定的值置为 true
 - 在 submit 方法中,要等待状态改变后再跳走,否则还会提示,但是 setState 是异步的,所以需要写在 setState 的回调函数中
 
import React, { Component } from 'react'import { Prompt } from 'react-router-dom'export default class UserAdd extends Component {constructor (props) {super()this.state = {name: '',show: false}}submit = () => {let localStr = localStorage.getItem('list')let list = localStr ? JSON.parse(localStr) : []list.push({id: Math.floor(Math.random() * 100),name: this.state.name})localStorage.setItem('list', JSON.stringify(list))// 此时已经正常保存了,可以跳走了,但是不需要提示了,所以把 when 属性绑定的属性值置为 falsethis.setState({show: false}, () => {this.props.history.push('/user/list')})// 要等待状态改变后再跳走,否则还会提示,但是 setState 是异步的,所以需要写在 setState 的回调函数中}render () {return (<div><h1>UserAdd</h1><Prompt when={this.state.show} message={location => `您确定要去 ${location.pathname}?`}></Prompt><div className="form-group"><label htmlFor="name" className="control-label"></label><input type="text" className="form-control"value={this.state.name} onChange={(e) => {// 判断 input 中的内容不为空表示用户输入过了,此时尚未保存,此时要调走需要提示;而 Prompt 要想弹出,需要把 when 属性绑定的值置为 trueif(e.target.value.length > 0) {this.setState({name: e.target.value, show: true})}}}/></div><div className="form-group"><button className="btn btn-success" onClick={this.submit}>添加</button></div></div>)}}
十、NotFound 页面
当路由不匹配时,我们希望实现成一个 404 页面,增加一个 404 组件;此外,配置一个 Route 这个 Route 的 component 设置为 404 的组件,但是不设置 path 属性;当所有其他 Route 的 path 都不匹配时就会自动匹配这个没有 path 的 Route;
- 新增一个 NotFound 组件 NotFound.js
 
import React, { Component } from 'react'export default class NotFound extends Component {render () {return (<h1>NOT FOUND</h1>)}}
- 在 index.js 中增加一个 Route
 
<Route component={NotFound} />
- 此时在页面中输入一个不存在的路由时就会显示 NotFound 组件
 
【发上等愿,结中等缘,享下等福,择高处立,寻平处住,向宽处行】
