一、安装路由:
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.params
let {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 属性绑定的属性值置为 false
this.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 属性绑定的值置为 true
if(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 组件