React 路由

01. 路由概述

目标:理解前端路由的作用

在传统的 web 应用中前端都是由多页面组成的,用户可以通过链接在不同的页面之间进行跳转。

为了提升用户体验,在现代的 web 应用中前端大多都是单页面应用程序,也就是只有一个 HTML 页面的应用程序。

即使是单页面应用程序我们也要满足用户可以在”不同页面之间”跳转的需求,所以前端路由应运而生。

前端路由是指让用户从一个视图导航(跳转)到另一个视图,此处的视图指的是表示页面的组件,也就是使用组件代替传统 web 应用中的 HTML 页面。

前端路由实际上就是 URL 路径与组件之间的映射关系,即不同的页面组件对应不同的 URL 路径,用户可以通过不同的 URL 路径访问不同的页面组件。

02. 基本使用

目标:掌握路由的基本用法


  • 下载 React Router 并准备页面组件
  • 创建路由规则并在应用中使用
  • Route 组件的另一种语法格式

① 下载 React Router 并准备页面组件

npm install react-router-dom@5.3.3
// src/pages/Home.js
import React, { Component } from "react";

export default class Home extends Component {
  render() {
    return <div>Home</div>;
  }
}
// src/pages/News.js
import React, { Component } from "react";

export default class News extends Component {
  render() {
    return <div>News</div>;
  }
}

② 创建路由规则并在应用中使用

// src/App.js
import React, { Component } from "react";
import { BrowserRouter, Route, Link } from "react-router-dom";
import Home from "./pages/Home";
import News from "./pages/News";

export default class App extends Component {
  render() {
    // 使用 BrowserRouter 组件包裹整个应用, 因为只有在它的内部才可以进行路由管理
    return (
      <BrowserRouter>
        {/* Link 组件用于创建链接, 最终会被渲染成 a 标记 */}
        {/* Link 组件的 to 属性用于指定路由规则中的访问路径 */}
        {/* Link 组件的 children 指定的是在页面中显示的导航链接的名称 */}
        <Link to="/home">首页</Link>
        <Link to="/news">新闻页</Link>
        {/* Route 组件用于配置路由规则, 指定 URL 路径与组件之间的对应关系 */}
        {/* Route 组件的 path 属性用于指定访问路径, component 属性用于指定访问路径对应的页面组件 */}
        {/* Route 组件写在哪, 匹配到的组件就渲染在哪 */}
        {/* Route 组件在没有匹配到任何组件的时候将会渲染 null */}
        <Route path="/home" component={Home} />
        <Route path="/news" component={News} />
      </BrowserRouter>
    );
  }
}

③ Route 组件的另一种语法格式

<Route path="/home">
  <Home />
</Route>
<Route path="/news">
  <News />
</Route>

03. 路由模式

目标:能够知道 React Router 中提供的两种路由模式

在 React Router 中提供了两种路由模式,分别为 HashRouter 路由模式和 BrowserRouter 路由模式。

BrowserRouter 路由模式使用的是 HTML5 的 history 历史记录对象实现的路由,原理是监听 window 对象的 popstate 事件。

http://localhost:3000/
http://localhost:3000/news

HashRouter 路由模式内部使用的是 URL 路径中的 Hash 值实现的路由,原理是监听 window 对象的 hashchange 事件。

http://localhost:3000/#/
http://localhost:3000/#/news

BrowserRouter 路由模式通过 createBrowserRouter 方法创建、HashRouter 路由模式通过 createHashRouter 方法创建。

import { createHashRouter, createBrowserRouter } from "react-router-dom";

在两种路由模式中官方更推荐使用 BrowserRouter 路由模式,如果你的应用要兼容低版本的 IE 浏览器,请使用 HashRouter 路由模式。

目标:掌握 NavLink 组件的用法

Link 组件用于创建普通链接,NavLink 用于创建导航链接。

当 NavLink 创建的链接被选中后,标记身上会被添加 active 类名,用于协助开发者实现链接高亮效果。

import { NavLink } from "react-router-dom";

<NavLink to="/home">首页</NavLink>
<NavLink to="/news">新闻页</NavLink>

默认的高亮类名为 active,可以通过 activeClassName 属性进行更改。

<NavLink activeClassName="selected" to="/home">首页</NavLink>
<NavLink activeClassName="selected" to="/news">新闻页</NavLink>

05. 路由匹配模式

目标:掌握 Route 组件中 path 属性的使用方式

Route 组件的 path 属性在匹配组件时存在两种匹配模式,即模糊匹配和精确匹配。

模糊匹配是指只要 path 属性的值被请求路径包裹即可匹配成功。

// 当请求路径为 /home 时以下所以路由规则都能匹配成功
<Route path="/" component={Home}/>
<Route path="/home" component={Home}/>

// 当请求路径为 /home/a/b 时以下所有路由规则都能匹配成功
<Route path="/" component={Home}/>
<Route path="/home" component={Home}/>
<Route path="/home/a" component={Home}/>
<Route path="/home/a/b" component={Home}/>

精确匹配是指 path 属性的值和请求路径完全相等才能匹配成功。

要实现精确匹配需要为 Route 组件添加 exact 属性。

// 只有访问路径为 / 时以下路由规则才可以匹配到
// 因为所有请求路径都是以 / 开头, 所以如果不是精确匹配, 那么以下路径规则在任何情况下都可以被匹配到
// 在 React 应用中, 绝大多数情况下根路由都是需要添加 exact 属性的
<Route path="/" component={Home} exact/>

06. 路由传参-查询参数

目标:掌握路由传参中查询参数的使用方式

路由传参是指从一个页面组件跳转到另一个页面组件时可以向目标组件传递参数。

比如从文章列表页跳转到文章详情页,我们需要将文章的 id 从文章列表页组件传入到文章详情页组件,因为文章详情页组件中要根据 id 获取文章详情。


  • 什么是查询参数
  • 准备文章列表页组件和文章详情页组件
  • 配置文章列表页组件和文章详情页组件对应的路由规则
  • 在文章列表页组件中找到文章列表并为文章链接加入 id 参数
  • 在文章详情页组件中获取路由参数 ( 文章 id )

① 什么是查询参数

URL 中问号后面的字符串被叫做查询参数

查询参数的格式为:参数=值&参数=值

也就是说 URL 中问号后面的内容就是为目标路由组件传递的参数。

http://www.example.com/?page=1&pageSize=10

② 准备文章列表页组件和文章详情页组件

// 文章列表页组件: src/pages/List.js
import React, { Component } from "react";
import { Link } from "react-router-dom";

export default class List extends Component {
  render() {
    return (
      <ul>
        <li>
          <Link to="">测试文章一</Link>
        </li>
        <li>
          <Link to="">测试文章二</Link>
        </li>
      </ul>
    );
  }
}
// 文章详情页组件: src/pages/Post.js
import React, { Component } from "react";

export default class Post extends Component {
  render() {
    return <div>文章详情页面</div>;
  }
}

③ 配置文章列表页组件和文章详情页组件对应的路由规则

// src/App.js
import React, { Component } from "react";
import { BrowserRouter, NavLink, Route, Link } from "react-router-dom";
import List from "./pages/List";
import Post from "./pages/Post";

export default class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <Route path="/list" component={List} />
        <Route path="/post" component={Post} />
      </BrowserRouter>
    );
  }
}

④ 在文章列表页组件中找到文章列表并为文章链接加入 id 参数

// src/pages/List.js
<Link to="/post?pid=1">测试文章一</Link>
<Link to="/post?pid=2">测试文章二</Link>
// src/pages/List.js
<Link to={{ pathname: "/post", search: "?pid=1" }}>测试文章一</Link>
<Link to={{ pathname: "/post", search: "?pid=2" }}>测试文章二</Link>

⑤ 在文章详情页组件中获取路由参数 ( 文章 id )

React Router 会为路由组件的 props 对象注入四个属性分别为 history、location、match、staticContext。

我们需要的查询参数在 location 属性对象中的 search 属性中。

// src/pages/Post.js
import React, { Component } from "react";

export default class post extends Component {
  render() {
    return <div>当前要获取的文章 id 为: {this.props.location.search}</div>;
  }
}

当获取到 search 属性的值时我们发现它是一个字符串,对于我们来说获取参数值并不方便,所以接下来要做的事情就是将 search 属性值解析为对象类型。

npm install qs@^6.11.0
// src/pages/Post.js
import React, { Component } from "react";
import qs from "qs";

export default class Post extends Component {
  render() {
    const search = qs.parse(this.props.location.search, {
      ignoreQueryPrefix: true,
    });
    return (
      <div>
        当前要获取的文章 id 为:
        {search.pid}
      </div>
    );
  }
}

07. 路由传参-路径参数

目标:掌握路由传参中路径参数的使用方式

① 什么是路径参数

http://localhost:3000/post/1
http://localhost:3000/post/2

② 在路由规则中配置路径参数

由于路径参数和请求路径本身是混合的写在一起的,所以在定义路由匹配规则时要将两者进行区分。

在请求路径后面以冒号开头的字符就表示路径参数,此处写的是路径参数的名称,在路由组件内部通过该名称获取真正的路由参数值。

<Route path="/post/:pid" component={Post} />

③ 在文章列表页组件中找到文章列表并为文章链接加入 id 参数

<Link to="/post/1">测试文章一</Link>
<Link to="/post/2">测试文章二</Link>

④ 在文章详情页组件中获取路由参数 ( 文章 id )

// src/pages/Post.js
import React, { Component } from "react";

export default class Post extends Component {
  render() {
    return <div>当前要获取的文章 id 为: {this.props.match.params.pid}</div>;
  }
}

08. Switch 组件

目标:掌握 Switch 组件的用法

问题:Route 组件从上到下对路由规则进行匹配,匹配到所有路由组件都会被渲染,但在绝大多数场景下我们只想渲染第一个匹配到的路由组件。

观察以下路由规则,请说出当访问 /post/introl 路径时,哪些路由组件会被匹配到?

// src/App.js
import Intro from "./pages/Intro";

export default class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <Route path="/post/intro" component={Intro} />
        <Route path="/post/:pid" component={Post} />
      </BrowserRouter>
    );
  }
}

通过运行代码我们发现当访问 /post/introl 路径时,Intro、Post 组件都会被渲染,但我们的需求是只渲染第一个匹配到的路由组件,该问题要如何解决呢?

通过 Switch 组件可以解决以上问题,Switch 组件的作用是一旦匹配到路由组件就停止继续向后匹配,即只渲染第一个匹配到的路由组件。

// src/App.js
import { Switch } from "react-router-dom";

export default class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <Switch>
          <Route path="/post/intro" component={Intro} />
          <Route path="/post/:pid" component={Post} />
        </Switch>
      </BrowserRouter>
    );
  }
}

在以上的代码中还有一个注意事项,就是两个路由规则的位置不能调换,因为调换以后 Introl 组件将永远不能被匹配到,所以越明确的路由规则越要向前放置。

09. 404 页面组件

目标:掌握在应用中添加 404 页面组件的方法

当用户访问了一个不存在的请求路径时,我们应该给出提示,提示用户的这个页面组件通常被叫做 404 页面组件。

// src/pages/NotFound.js
import React, { Component } from "react";

export default class NotFound extends Component {
  render() {
    return <div>您访问的页面不存在</div>;
  }
}
// src/App.js
import NotFound from "./pages/NotFound";

// 当 Route 组件没有 path 属性时, 它可以匹配到任何请求路径
// 路由规则从上到下依次匹配, 如果没有规则被匹配到, 我们让最后一条规则进行匹配, 最后一条规则渲染的组件就是 404 页面组件
export default class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <Switch>
          <Route path="/list" component={List} />
          <Route path="/post/:pid" component={Post} />
          <Route component={NotFound} />
        </Switch>
      </BrowserRouter>
    );
  }
}

10. 编程式导航 push

目标:掌握编程式导航 push 方法的使用方式

编程式导航是指通过 JavaScript 的方式进行页面跳转。

history.push:在跳转页面时将跳转行为添加到浏览器的历史记录中,方便历史记录的回退和前进。

// src/pages/List.js
import React, { Component } from "react";

export default class List extends Component {
  render() {
    return (
      <>
        <button onClick={() => this.props.history.push("/post/1")}>跳转到 id 为 1 的文章页面</button>
        <button onClick={() => this.props.history.push({ pathname: "/post/2" })}>跳转到 id 为 2 的文章页面</button>
      </>
    );
  }
}

11. 编程式导航 replace

目标:掌握编程式导航 replace 方法的使用方式

history.replace:在跳转页面时不会将当前的跳转行为添加到历史记录中。

// src/pages/List.js
import React, { Component } from "react";

export default class List extends Component {
  render() {
    return (
      <>
        <button onClick={() => this.props.history.replace("/post/1")}>跳转到 id 为 1 的文章页面</button>
        <button onClick={() => this.props.history.replace({ pathname: "/post/2" })}>跳转到 id 为 2 的文章页面</button>
      </>
    );
  }
}

12. 重定向 Redirect

目标:掌握重定向组件的用法

需求:当用户访问 / 时重定向到 /home

import React, { Component } from "react";
import { BrowserRouter, Redirect, Route, Switch } from "react-router-dom";
import Home from "./pages/Home";
import News from "./pages/News";

export default class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <Switch>
          <Redirect from="/" to="/home" exact />
          <Route path="/home" component={Home} />
          <Route path="/news" component={News} />
        </Switch>
      </BrowserRouter>
    );
  }
}

需求:当用户访问 / 时判断用户是否登录,如果登录则为用户重定向至 /dashboard,否则渲染 home 组件。

import React, { Component } from "react";
import { BrowserRouter, Redirect, Route, Switch } from "react-router-dom";
import Dashboard from "./pages/Dashboard";
import Home from "./pages/Home";

const isLoggedIn = true;

export default class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <Switch>
          <Route path="/" exact>
            {isLoggedIn ? <Redirect to="/dashboard" /> : <Home />}
          </Route>
          <Route path="/dashboard" component={Dashboard} replace/>
        </Switch>
      </BrowserRouter>
    );
  }
}

需求:在登录页面中判断用户是否登录,如果登录重定向到 dashboard 页面。

import React, { Component } from "react";
import { Redirect } from "react-router-dom";

const isLoggedIn = true;

export default class login extends Component {
  render() {
    if (isLoggedIn) {
      return <Redirect to="/dashboard" />;
    }
    return <div>login</div>;
  }
}

13. 嵌套路由

目标:掌握嵌套路由的使用方式

嵌套路由是指在路由组件中继续嵌套路由组件,就是俗称的子路由 ( 二级路由、三级路由、… … )。

是否使用嵌套路由取决于你的页面结构。

比如在下图中,通过 /user 访问了用户组件,在用户组件中要求显示用户个人信息和用户账户信息,此时个人信息和账户信息就可以使用嵌套路由来实现。


  • 创建用户组件、用户个人信息组件、用户账户信息组件
  • 配置用户组件、用户个人信息组件、用户账号信息组件的路由规则
  • 在用户组件中添加路由插座组件

① 创建用户组件、用户个人信息组件、用户账户信息组件

// 用户组件: src/pages/User.js
import React, { Component } from "react";

export default class User extends Component {
  render() {
    return <div>User</div>;
  }
}
// 用户个人信息组件: src/pages/UserProfile.js
import React, { Component } from "react";

export default class UserProfile extends Component {
  render() {
    return <div>UserProfile</div>;
  }
}
// 用户账户信息组件: src/pages/UserAccount.js
import React, { Component } from "react";

export default class UserAccount extends Component {
  render() {
    return <div>UserAccount</div>;
  }
}

② 配置用户组件、用户个人信息组件、用户账号信息组件的路由规则

// src/App.js
import React, { Component } from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import User from "./pages/User";

export default class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <Switch>
          <Route path="/user" component={User} />
        </Switch>
      </BrowserRouter>
    );
  }
}
// src/pages/User.js
import React, { Component } from "react";
import { Link, Route } from "react-router-dom";
import UserAccount from "./UserAccount";
import UserProfile from "./UserProfile";

export default class User extends Component {
  render() {
    return (
      <>
        <div>User</div>
        <Link to="/user/profile">用户信息</Link>
        <Link to="/user/account">账号信息</Link>
        <Route path="/user/profile" component={UserProfile} />
        <Route path="/user/account" component={UserAccount} />
      </>
    );
  }
}

③ 将子级路由组件中配置的路由规则地址和链接访问地址更改为动态的

目前在 User 组件中配置子级路由匹配路径时父级路由的匹配路径是硬编码的,一旦父级路由的匹配路径发生了变化,子级路由组件处也需要跟着更改。

在 User 组件编写的 Link 链接地址也是同样的问题,如果父级路由匹配路径发生了更改,子级路由组件中编写 Link 链接地址也需要跟着更改。

所以在子级路由组件中我们要动态获取父级路由的匹配路径和访问链接,防止父级路由的匹配路径发生变化以后子级路由组件跟着一起更改。

// src/pages/User.js
export default class User extends Component {
  render() {
    // 父级路由的访问地址
    const url = this.props.match.url;
    // 父级路由规则中定义的匹配路径
    const path = this.props.match.path;
    return (
      <>
        <div>User</div>
        <Link to={`${url}/profile`}>用户信息</Link>
        <Link to={`${url}/account`}>账号信息</Link>
        <Route path={`${path}/profile`} component={UserProfile} />
        <Route path={`${path}/account`} component={UserAccount} />
      </>
    );
  }
}

在以上代码中 url 和 path 的值是一样的所以并没有看出它们有什么区别,当路由中有参数时 url 和 path 的区别就能够被看出来了。

match.path  /user/:id   路由匹配规则
match.url   /user/1     路由访问地址

14. 布局组件

目标:创建 MainLayout、AdminLayout 组件、前台页面使用 MainLayout 布局组件、后台页面使用 AdminLayout 布局组件


  • 创建前台页面使用的布局组件、前台页面的头部组件、底部组件
  • 创建后台页面使用的布局组件、后台页面的头部组件、侧边栏组件
  • 创建前台首页组件、新闻页组件、创建后台管理首页组件、新闻管理页面
  • 配置前台页面路由规则和后台页面路由规则

① 创建前台页面使用的布局组件、前台页面的头部组件、底部组件

// 前台页面头部组件: src/components/main/MainHeader.js
import React, { Component } from "react";

export default class MainHeader extends Component {
  render() {
    return <div>MainHeader</div>;
  }
}
// 前台页面底部组件: src/components/main/MainFooter.js
import React, { Component } from "react";

export default class MainFooter extends Component {
  render() {
    return <div>MainFooter</div>;
  }
}
// 前台页面布局组件: src/components/main/MainLayout.js
import React, { Component } from "react";
import MainFooter from "./MainFooter";
import MainHeader from "./MainHeader";

export default class MainLayout extends Component {
  render() {
    return (
      <>
        <MainHeader />
        {this.props.children}
        <MainFooter />
      </>
    );
  }
}

② 创建后台页面使用的布局组件、后台页面的头部组件、侧边栏组件

// 后台头部组件: src/pages/components/AdminHeader.js
import React, { Component } from "react";

export default class AdminHeader extends Component {
  render() {
    return <div>AdminHeader</div>;
  }
}
// 后台侧边栏组件: src/pages/components/AdminSidebar.js
import React, { Component } from "react";

export default class AdminSidebar extends Component {
  render() {
    return <div>AdminSidebar</div>;
  }
}
// 后台布局组件: src/pages/components/AdminLayout.js
import React, { Component } from "react";
import AdminHeader from "./AdminHeader";
import AdminSidebar from "./AdminSidebar";

export default class AdminLayout extends Component {
  render() {
    return (
      <>
        <AdminHeader />
        {this.props.children}
        <AdminSidebar />
      </>
    );
  }
}

③ 创建前台首页组件、新闻页组件、创建后台管理首页组件、新闻管理页面

// 前台首页页面组件: src/pages/main/MainHome.js
import React, { Component } from "react";

export default class MainHome extends Component {
  render() {
    return <div>MainHome</div>;
  }
}
// 前台新闻页页面组件: src/pages/main/MainNews.js
import React, { Component } from "react";

export default class MainNews extends Component {
  render() {
    return <div>MainNews</div>;
  }
}
// 后台首页页面组件: src/pages/admin/AdminHome.js
import React, { Component } from "react";

export default class AdminHome extends Component {
  render() {
    return <div>AdminHome</div>;
  }
}
// 后台新闻页页面组件: src/pages/admin/AdminNews.js
import React, { Component } from "react";

export default class AdminNews extends Component {
  render() {
    return <div>AdminNews</div>;
  }
}

④ 配置前台页面路由规则和后台页面路由规则

import React, { Component } from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import AdminLayout from "./components/admin/AdminLayout";
import MainLayout from "./components/main/MainLayout";
import AdminHome from "./pages/admin/AdminHome";
import AdminNews from "./pages/admin/AdminNews"
import MainHome from "./pages/main/MainHome";
import MainNews from "./pages/main/MainNews";

export default class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <Switch>
          <Route path="/admin">
            <AdminLayout>
              <Switch>
                <Route path="/admin" component={AdminHome} exact />
                <Route path="/admin/news" component={AdminNews} />
              </Switch>
            </AdminLayout>
          </Route>
          <Route path="/">
            <MainLayout>
              <Route path="/" component={MainHome} exact />
              <Route path="/news" component={MainNews} />
            </MainLayout>
          </Route>
        </Switch>
      </BrowserRouter>
    );
  }
}

15. 路由守卫组件

目标:了解什么是路由守卫组件

路由守卫是对路由组件的保护,它是指在进入到某一个路由之前,判断当前用户是否有权进入,如果有则渲染对应的路由组件、如果没有再进行相应的操作。

需求:通过路由守卫组件验证用户是否登录,如果登录渲染目标路由组件,否则重定向到登录页面。

import RouteGuard from "./components/RouteGuard";
import Dashboard from "./pages/Dashboard";

export default class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <RouteGuard path="/dashboard" component={Dashboard} replace />
      </BrowserRouter>
    );
  }
}
// src/components/RouteGuard.js
import React, { Component } from "react";
import { Redirect, Route } from "react-router-dom";

const isLoggedIn = false;

export default class RouteGuard extends Component {
  render() {
    if (isLoggedIn) {
      return <Route {...this.props} />;
    }
    return <Redirect to="/login" />;
  }
}

目标:封装路由组件,支持异步验证。

// src/App.js
import RouteGuard from "./components/RouteGuard";

async function isAuth() {
  return await new Promise((resolve) => setTimeout(resolve, 2000));
}

async function isAdmin() {
  throw new Error();
}

export default class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <RouteGuard
            guards={[isAuth, isAdmin]}
            onWaiting={<div>loading...</div>}
            onRejected={<Redirect to="/login" />}
            path="/dashboard"
            component={Dashboard}
            replace
          />
      </BrowserRouter>
    );
  }
}
// src/components/RouteGuard.js
import React, { Component } from "react";
import { Route } from "react-router-dom";

export default class RouteGuard extends Component {
  state = {
    // 是否正在验证
    waiting: true,
    // 是否验证成功
    success: false,
  };

  // 组件挂载完成之后
  componentDidMount() {
    Promise.all(this.props.guards.map((guard) => guard()))
      .then(() => this.setState({ success: true }))
      .catch(() => this.setState({ success: false }))
      .finally(() => this.setState({ waiting: false }));
  }
  render() {
    const { guards, onWaiting, onRejected, ...rest } = this.props;
    if (this.state.waiting) return onWaiting;
    if (this.state.success) return <Route {...rest} />;
    return onRejected;
  }
}

目标:封装路由组件,支持批量路由组件验证

// src/App.js
export default class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <RouteGuard 
          path="/dashboard" 
          guards={[isAuth, isAdmin]} 
          onWaiting={<div>loading...</div>} 
          onRejected={<Redirect to="/login" />}
        >
            <Route path="/dashboard" component={Dashboard} replace />
        </RouteGuard>
      </BrowserRouter>
    );
  }
}
// src/components/RouteGuard.js
import React, { Component } from "react";
import { Route } from "react-router-dom";

export default class RouteGuard extends Component {
  state = {
    // 是否正在验证
    waiting: true,
    // 是否验证成功
    success: false,
  };

  // 组件挂载完成之后
  componentDidMount() {
    Promise.all(this.props.guards.map((guard) => guard()))
      .then(() => this.setState({ success: true }))
      .catch(() => this.setState({ success: false }))
      .finally(() => this.setState({ waiting: false }));
  }

  render() {
    const { onWaiting, onRejected, guards, ...rest } = this.props;
    if (this.state.waiting) return onWaiting;
    if (this.state.success) {
      if (typeof this.props.children !== "undefined") {
        return this.props.children;
      } else {
        return <Route {...rest} />;
      }
    } else {
      return onRejected;
    }
  }
}

16. 路由组件懒加载

// src/App.js
import React, { Component, lazy, Suspense } from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";

const Home = lazy(() => import(/* webpackChunkName: "home" */ "./pages/Home"));
const News = lazy(() => import(/* webpackChunkName: "news" */ "./pages/News"));

export default class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <Suspense fallback={<div>loading...</div>}>
          <Switch>
            <Route path="/" component={Home} exact></Route>
            <Route path="/news" component={News} />
          </Switch>
        </Suspense>
      </BrowserRouter>
    );
  }
}