一、安装路由:

1. 安装

  1. npm install react-router-dom --save

2. 容器组件

安装成功以后,这里我们下载好后需要他内部的路由容器组件,主要包含

  • BrowserRouter 浏览器自带的H5 API, restful 风格,需要配合后台;
  • HashRouter 使用 hash 方式进行路由,路径后均有#
  • MemoryRouter 在内存中管理 history ,地址栏不会变化。在 reactNative 中使用。

二、基本使用

2.1 跑通路由

我们来声明三个组件 Home , User , Profile 希望访问不同的路径可以实现显示不同的组件

  • Home.js
  1. import React, { Component } from 'react'
  2. export default class Home extends Component {
  3. render () {
  4. return (<div>
  5. HOME
  6. </div>)
  7. }
  8. }
  • User.js
  1. import React, { Component } from 'react'
  2. export default class User extends Component {
  3. render () {
  4. return (<div>
  5. User
  6. </div>)
  7. }
  8. }
  • Profile.js
  1. import React, { Component } from 'react'
  2. export default class Profile extends Component {
  3. render () {
  4. return (<div>
  5. Profile
  6. </div>)
  7. }
  8. }
  • index.js
  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import './index.css';
  4. import App from './App';
  5. import { HashRouter, Router, Route } from 'react-router-dom'
  6. import Home from './components/Home'
  7. import User from './components/User'
  8. import Profile from './components/Profile'
  9. ReactDOM.render(<HashRouter>
  10. <div>
  11. <Route path="/home" component={Home} />
  12. <Route path="/profile" component={Profile} />
  13. <Route path="/user" component={User} />
  14. </div>
  15. </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
  1. import React from 'react';
  2. import logo from './logo.svg';
  3. import './App.css';
  4. import 'bootstrap/dist/css/bootstrap.css'
  5. import { Link } from 'react-router-dom'
  6. class App extends React.Component {
  7. render () {
  8. return (<div>
  9. <div className="navbar-inverse navbar">
  10. <div className="container-fluid">
  11. <div className="navbar-header">
  12. <div className="navbar-brand">
  13. 用户管理系统
  14. </div>
  15. <ul className="navbar-nav nav">
  16. <li>
  17. <Link to='/home'>首页</Link>
  18. </li>
  19. <li>
  20. <Link to='/user'>用户管理</Link>
  21. </li>
  22. <li>
  23. <Link to='/profile'>个人中心</Link>
  24. </li>
  25. </ul>
  26. </div>
  27. </div>
  28. </div>
  29. <div className="container">
  30. <div className="row">
  31. <div className="col-md-12">
  32. {
  33. this.props.children
  34. }
  35. </div>
  36. </div>
  37. </div>
  38. </div>)
  39. }
  40. }
  41. export default App;
  • index.js

注意 HashRouter 是在 App 的外面

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import './index.css';
  4. import App from './App';
  5. import { HashRouter, Router, Route } from 'react-router-dom'
  6. import Home from './components/Home'
  7. import User from './components/User'
  8. import Profile from './components/Profile'
  9. ReactDOM.render(
  10. <HashRouter>
  11. <App>
  12. <div>
  13. <Route path="/home" component={Home} />
  14. <Route path="/profile" component={Profile} />
  15. <Route path="/user" component={User} />
  16. </div>
  17. </App>
  18. </HashRouter>
  19. , document.getElementById('root'));

四、二级路由

很多情况下,在一个导航下还有二级导航,如 /user 下还有 /user/list 和 /user/add ;类似 VueRouter 的嵌套路由;

4.1 实现二级导航

  • 就是在某个一级路由中继续嵌套路由,例如在 User.js 中;
  • User.js
  1. import React, { Component } from 'react'
  2. import { Link, Route } from 'react-router-dom'
  3. import UserAdd from './UserAdd'
  4. import UserList from './UserList'
  5. export default class User extends Component {
  6. render () {
  7. return (<div>
  8. <h1>
  9. User
  10. </h1>
  11. <div className="row">
  12. <div className="col-md-2">
  13. <ul className="nav nav-stacked">
  14. <li><Link to='/user/list'>用户列表</Link></li>
  15. <li><Link to='/user/add'>增加用户</Link></li>
  16. </ul>
  17. </div>
  18. <div className="col-md-10">
  19. <Route path='/user/list' component={UserList}></Route>
  20. <Route path='/user/add' component={UserAdd}></Route>
  21. </div>
  22. </div>
  23. </div>)
  24. }
  25. }
  • UserAdd.js
  1. import React, { Component } from 'react'
  2. export default class UserAdd extends Component {
  3. render () {
  4. return (<div>
  5. <h1>
  6. UserAdd
  7. </h1>
  8. </div>)
  9. }
  10. }
  • UserList.js
  1. import React, { Component } from 'react'
  2. export default class UserList extends Component {
  3. render () {
  4. return (<div>
  5. <h1>UserList</h1>
  6. </div>)
  7. }
  8. }

五、动态路由和路由跳转

进入到添加页,可以实现用户添加,并且可以跳转列表页,列表页要渲染出已经存在的用户列表,页面间的通信方式我们采用 localStorage。并且点击某个用户我们可以进入到详情页中;

5.1 改造 UserAdd.js

  1. import React, { Component } from 'react'
  2. export default class UserAdd extends Component {
  3. constructor (props) {
  4. super()
  5. this.state = {
  6. name: ''
  7. }
  8. }
  9. submit = () => {
  10. let localStr = localStorage.getItem('list')
  11. let list = localStr ? JSON.parse(localStr) : []
  12. list.push({
  13. id: Math.floor(Math.random() * 100),
  14. name: this.state.name
  15. })
  16. localStorage.setItem('list', JSON.stringify(list))
  17. this.props.history.push('/user/list')
  18. }
  19. render () {
  20. return (<div>
  21. <h1>
  22. UserAdd
  23. </h1>
  24. <div className="form-group">
  25. <label htmlFor="name" className="control-label"></label>
  26. <input type="text" className="form-control" value={this.state.name} onChange={(e) => this.setState({name: e.target.value})}/>
  27. </div>
  28. <div className="form-group">
  29. <button className="btn btn-success" onClick={this.submit}>添加</button>
  30. </div>
  31. </div>)
  32. }
  33. }
  • 在 添加 按钮中我们通过 props 的 API,this.props.history.push(‘/user/list’) 这就是采用编程式的路由跳转,所有通过路由渲染的的组件都有一些路由的属性;

5.2 改造 UserList.js

  1. import React, { Component } from 'react'
  2. import { Link } from 'react-router-dom'
  3. export default class UserList extends Component {
  4. constructor () {
  5. super()
  6. this.state = {
  7. list: []
  8. }
  9. }
  10. componentWillMount () {
  11. let listStr = localStorage.getItem('list')
  12. let list = listStr ? JSON.parse(listStr) : []
  13. if (list.length) this.setState({list})
  14. }
  15. render () {
  16. return (<div>
  17. <h1>UserList</h1>
  18. <ul className="list-group">
  19. {
  20. this.state.list.map((item, index) => {
  21. return <li key={index} className='list-group-item'>
  22. <Link to={`/user/detail/${item.id}`}>id: {item.id}; Username: {item.name}</Link>
  23. </li>
  24. })
  25. }
  26. </ul>
  27. </div>)
  28. }
  29. }
  • 这里我们新增加了一个路由,点击用户名的时候还要跳转到详情页;它是一个动态路由,即 /user/detail 后面的部分是不固定的,传递的是参数;

5.3 新增 UserDetail.js 用于根据动态路由中的 id 展示对应的用户信息;

  • 这里需要注意如何从动态路由中获取到 id,通过 props 上的 match 对象中的 params 对象获取;this.props.match.params 是一个对象,其中包含了当前匹配到动态路由中的参数;
  1. import React, { Component } from 'react'
  2. export default class UserDetail extends Component {
  3. render () {
  4. let { id } = this.props.match.params
  5. let {name } = JSON.parse(localStorage.getItem('list')).find(i => +i.id === +id)
  6. return (<div>
  7. <h1>UserDetail</h1>
  8. <h1>
  9. ID {id};
  10. NAME: {name}
  11. </h1>
  12. </div>)
  13. }
  14. }

六、路由匹配

路由的匹配规则是模糊匹配的,只要前一部分的路由匹配成功就会展示对应的组件,有的时候我们希望匹配有些限制,比如说严格对某个路径进行匹配,或者匹配到某一个路径后就不再匹配了;

在 index.js 中新增加两个路由

  1. ReactDOM.render(
  2. <HashRouter>
  3. <App>
  4. <div>
  5. + <Route path='/' render={() => <h1>首页</h1>}></Route>
  6. + <Route path='/:name' render={() => <h1>MABIN</h1>}></Route>
  7. <Route path="/home" component={Home} />
  8. <Route path="/profile" component={Profile} />
  9. <Route path="/user" component={User} />
  10. </div>
  11. </App>
  12. </HashRouter>
  13. , document.getElementById('root'));

6.1 exact 属性

  • 此时我们发现访问 /home 时, 首页、MABIN、Home 组件都显示了,而我们只需往 / 时才显示首页,此时我们需要精确匹配,我们需要在 / 的 Route 上增加 exact 属性;此时,只有访问 / 时才会显示 首页;
  • 此外 Route 组件还接收一个 render 函数,这个 render 可以返回 jsx 元素,当 path 匹配时就会渲染 render 返回的 jsx 元素,可以充当组件的角色;
  1. <Route exact path='/' render={() => <h1>首页</h1>}></Route>

6.2 Switch 组件

但是我们发现,现在访问 /home 时,依然有两个路由会被匹配到,我们希望匹配到一个后停止匹配,不再继续匹配下一个路由,我们需要使用 Switch 组件;Switch 组件也是 react-router-dom 的组件,需要导出才能使用;

  • 改造 index.js
  1. ReactDOM.render(
  2. <HashRouter>
  3. <App>
  4. <Switch>
  5. <Route exact path='/' render={() => <h1>首页</h1>}></Route>
  6. <Route path='/:name' render={() => <h1>MABIN</h1>}></Route>
  7. <Route path="/home" component={Home} />
  8. <Route path="/profile" component={Profile} />
  9. <Route path="/user" component={User} />
  10. </Switch>
  11. </App>
  12. </HashRouter>
  13. , document.getElementById('root'));

此时,我们访问 /home 时,只能出现 MABIN 永远不会出现 Home 组件了;

  • 注意此时,/:name 是个动态路由,使用 Switch 后,我们发现再切路由无论是 /user 还是 /profile 都会只展示 MABIN,这是因为 Router 认为 /user 和 /profile 也符合 /:name;

七、受保护的路由

在真实的项目中,我们会对一些路由进行保护,例如登录后才能访问,类似 VueRouter 的导航守卫;

我们举个例子,我们新增一个登录的路由,我们本地存储一个变量来表示是否登录过了,点击登录按钮将本地变量改为成功状态,此后才能访问用户列表;

当路由切换到 /user 时,我们需要判断是否登录或者是否有权限,如果没登录就需要跳转到登录页,该功能需要依靠高阶组件实现;

7.1 Protected.js

通过高阶组件包装 Route

  1. import React, { Component } from 'react'
  2. import { Route, Redirect } from 'react-router-dom'
  3. export default ({component: Component, ...others}) => {
  4. return <Route {...others} render={(props) => {
  5. // props 是 router 对象,包含了当前路由的信息
  6. return localStorage.getItem('loginSystem')
  7. ? <Component {...props} />
  8. : <Redirect to={{pathname: '/login', from: props.match.url}} />
  9. }
  10. }></Route>
  11. }

Redirect 组件是用来重定向的,我们新增 from 属性来记录当前匹配的 url ,为了保证登录后可以再跳回到当前匹配的路径

  • 增加 Login.js 组件
  1. import React, { Component } from 'react'
  2. export default class Login extends Component {
  3. render () {
  4. console.log(this.props.location)
  5. return (<div>
  6. <input type="text" className="form-control"/>
  7. <button className="btn btn-success" onClick={() => {
  8. localStorage.setItem('loginSystem', true);
  9. this.props.history.push(this.props.location.from)
  10. }}>登录</button>
  11. </div>)
  12. }
  13. }
  • 修改 index.js
  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import './index.css';
  4. import App from './App';
  5. import { HashRouter, Route, Switch } from 'react-router-dom'
  6. import Home from './components/Home'
  7. import User from './components/User'
  8. import Profile from './components/Profile'
  9. import Login from './components/Login'
  10. import PrivateRoute from './components/Protected'
  11. ReactDOM.render(
  12. <HashRouter>
  13. <App>
  14. <Switch>
  15. <Route exact path='/' render={() => <h1>首页</h1>}></Route>
  16. <Route path="/home" component={Home} />
  17. <PrivateRoute path="/profile" component={Profile} />
  18. <PrivateRoute path="/user" component={User} />
  19. <Route path='/login' component={Login} />
  20. </Switch>
  21. </App>
  22. </HashRouter>
  23. , document.getElementById('root'));
  • 经过上面的修改后,我们在默认情况下点击 profile 和 user 会被重定向到登录页,当我们登录后会跳转回去,此时就实现了保护路由;

八、自定义菜单

通常情况下,我们会给激活的菜单增加选中样式,此时已然需要使用高阶组件进行包装

自定义 MenuLink 组件来替换 Link 组件,并且在其内部我们可以判断是否增加了激活状态;

  • MenuLink.js

这里有一个

  1. import React, { Component } from 'react'
  2. import { Route, Link } from 'react-router-dom'
  3. export default ({ to, label }) => {
  4. return <Route path={to} children={(props) => {
  5. return <li className={props.match ? 'active' : ''}>
  6. <Link to={to}>{label}</Link>
  7. </li>
  8. }
  9. }></Route>
  10. }
  • 修改 App.js
  1. import React from 'react';
  2. import logo from './logo.svg';
  3. import './App.css';
  4. import 'bootstrap/dist/css/bootstrap.css'
  5. import {Link} from 'react-router-dom'
  6. import MenuLink from './components/MenuLink'
  7. class App extends React.Component {
  8. render() {
  9. return (<div>
  10. <div className="navbar-inverse navbar">
  11. <div className="container-fluid">
  12. <div className="navbar-header">
  13. <div className="navbar-brand">
  14. 用户管理系统
  15. </div>
  16. <ul className="navbar-nav nav">
  17. <MenuLink to='/home' label='首页'/>
  18. <MenuLink to='/user' label='用户管理'/>
  19. <li>
  20. <Link to='/profile'>个人中心</Link>
  21. </li>
  22. </ul>
  23. </div>
  24. </div>
  25. </div>
  26. <div className="container">
  27. <div className="row">
  28. <div className="col-md-12">
  29. {
  30. this.props.children
  31. }
  32. </div>
  33. </div>
  34. </div>
  35. </div>)
  36. }
  37. }
  38. export default App;
  • 在 App.js 中增加选中样式:
  1. li.active a {
  2. background: red!important;
  3. color: red;
  4. }
  • 此时我们选中首页和用户管理时,都会有对应选中样式;

九、Prompt

有的时候,当用户在某个页面进行了一些操作,但是他没有保存就要去另一个页面,我们很有可能要提示一下用户,是否确认跳转;此时我们可以使用 react-router-dom 的 Prompt 组件;

  • Prompt 组件在用户要从某个页面离开时,提示用户;
    • Prompt 有一个 when 属性,如果为 true 离开时才提示,如果为false不提示
    • Prompt 有一个 message 属性,值是一个函数,接收一个要去往的路由信息对象,返回离开当前页面时的提示信息

例如我们修改一下新增用户的组件 AddUser.js

  1. 在 input 的 onChange 事件中判断 input 中的内容不为空表示用户输入过了,此时尚未保存,此时跳走需要提示;而 Prompt 要想弹出,需要把 when 属性绑定的值置为 true
  2. 在 submit 方法中,要等待状态改变后再跳走,否则还会提示,但是 setState 是异步的,所以需要写在 setState 的回调函数中
  1. import React, { Component } from 'react'
  2. import { Prompt } from 'react-router-dom'
  3. export default class UserAdd extends Component {
  4. constructor (props) {
  5. super()
  6. this.state = {
  7. name: '',
  8. show: false
  9. }
  10. }
  11. submit = () => {
  12. let localStr = localStorage.getItem('list')
  13. let list = localStr ? JSON.parse(localStr) : []
  14. list.push({
  15. id: Math.floor(Math.random() * 100),
  16. name: this.state.name
  17. })
  18. localStorage.setItem('list', JSON.stringify(list))
  19. // 此时已经正常保存了,可以跳走了,但是不需要提示了,所以把 when 属性绑定的属性值置为 false
  20. this.setState({
  21. show: false
  22. }, () => {
  23. this.props.history.push('/user/list')
  24. })
  25. // 要等待状态改变后再跳走,否则还会提示,但是 setState 是异步的,所以需要写在 setState 的回调函数中
  26. }
  27. render () {
  28. return (<div>
  29. <h1>
  30. UserAdd
  31. </h1>
  32. <Prompt when={this.state.show} message={location => `您确定要去 ${location.pathname}?`}></Prompt>
  33. <div className="form-group">
  34. <label htmlFor="name" className="control-label"></label>
  35. <input type="text" className="form-control"
  36. value={this.state.name} onChange={(e) => {
  37. // 判断 input 中的内容不为空表示用户输入过了,此时尚未保存,此时要调走需要提示;而 Prompt 要想弹出,需要把 when 属性绑定的值置为 true
  38. if(e.target.value.length > 0) {
  39. this.setState({name: e.target.value, show: true})
  40. }
  41. }
  42. }/>
  43. </div>
  44. <div className="form-group">
  45. <button className="btn btn-success" onClick={this.submit}>添加</button>
  46. </div>
  47. </div>)
  48. }
  49. }

十、NotFound 页面

当路由不匹配时,我们希望实现成一个 404 页面,增加一个 404 组件;此外,配置一个 Route 这个 Route 的 component 设置为 404 的组件,但是不设置 path 属性;当所有其他 Route 的 path 都不匹配时就会自动匹配这个没有 path 的 Route;

  • 新增一个 NotFound 组件 NotFound.js
  1. import React, { Component } from 'react'
  2. export default class NotFound extends Component {
  3. render () {
  4. return (<h1>
  5. NOT FOUND
  6. </h1>)
  7. }
  8. }
  • 在 index.js 中增加一个 Route
  1. <Route component={NotFound} />
  • 此时在页面中输入一个不存在的路由时就会显示 NotFound 组件