Skip to content

路由

version 5

Adding a Router

Create React App doesn't prescribe a specific routing solution, but React Router is the most popular one.

To add it, run:

bash
npm install --save react-router-dom

Alternatively you may use yarn:

bash
yarn add react-router-dom

To try it, delete all the code in src/App.js and replace it with any of the examples on its website. The Basic Example is a good place to get started.

快速开始

https://reactrouter.com/web/guides/quick-start

在实际项目中通常都会有多个页面存在,可以通过链接来进行切换,达到多页面的效果。要实现这个功能,我们需要借助于 React 的路由模块 react-router-dom 来实现,它可以让我们监听浏览器地址的变化,并且解析这个 URL 对象,然后 router 根据 URL 的路径匹配到路由对应页面组件,最后正确地渲染对应地组件,这个就是路由工作的基本原理。

我们平时会使用的主要是两种路由模式:

  • HashRouter:这种模式基于浏览器 location 的 hash 片段来实现,实现比较简单,不需要服务器的支持,缺点是 url 样式不够优雅,而且 hash 参数容易丢失,如下:

    http://example.com/#/home/files
  • BrowserRouter:这种模式基于浏览器的 history API,可以让我们创建一个像 http://example.com/home/files 这样真实的 URL,而且切换 url 不会引起页面的刷新,用户体验比较好,是我们比较推荐的路由方式,不过这种模式需要服务器比如 Nginx 的支持,因为路径 /home/files 只是一个前端定义的路由,当用户刷新页面的时候浏览器会去向服务器请求这个资源,服务器因为没有对应的这个资源,就会返回 404,导致页面无法显示,所以需要 Nginx 将所有 404 的请求返回入口文件 /index.html,大概配置如下

    plain
    location / {
      root d:/www;
      try_files $uri $uri/ /index.html;
    }

路由的定义

我们可以把 React 组件分为路由组(页面)件和普通组件,路由组件对应的就是一个路由页面,我们可以用下面的方式来定义一个路由组件 About

ts
import * as React from "react";
import { RouteComponentProps, Link } from "react-router-dom";

export default class About extends React.Component<RouteComponentProps> {
  render() {
    return <h1>About</h1>;
  }
}

这里我们要给组件添加一个 props 的泛型 RouteComponentProps,添加之后 TS 就可以识别到传入的和路由控制相关的 props 参数,具体参考示例代码

然后我们需要定义一个根组件 App,用来确定路由和组件的映射关系,如下

ts
import * as React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";

import Main from "../Main";
import About from "../About";

export default class App extends React.Component {
  render() {
    return (
      <BrowserRouter>
        <div className="container">
          <Switch>
            <Route path="/" exact component={Main} />
            <Route path="/about" component={About} />
          </Switch>
        </div>
      </BrowserRouter>
    );
  }
}

在上面的代码中我们定义了两个路由规则,放在了 Switch 中,路由规则的定义通过 Route 组件来进行,它有两个关键的参数,分别是:

  • path:要匹配的 URL 路径
  • component:该路由对应的页面组件

需要注意的是,path 的路径默认会匹配到子路径,例如 /detail 也会匹配到 /detail/123,如果不像匹配子路径,需要加上 exact 属性。

Switch 会根据当前的 URL 路径匹配它下面的 Route,然后将匹配到的第一个组件渲染到当前位置。

带参数的路由

有时候我们的页面会接收一些参数,比如根据传入的关键词去进行搜索,react-router-dom 支持路由参数,我们可以用下面的方式来定义

ts
<Route path="/search/:keyword" component={Search} />

然后我们可以在对应的路由组件 Search 中来接收这些参数,通过下面的方式来进行

首先我们需要定义合适的 Props 类型,RouteComponentProps 也支持传入参数泛型,如下

ts
interface Params {
  keyword: string;
}

type Props = RouteComponentProps<Params>;

export default class Search extends React.Component<Props> {
  render() {
    return <div>{this.props.match.params.keyword}</div>;
  }
}

我们首先定义了当前路由组件接收的参数类型 Params,跟我们在 Route 中定义的参数一致,然后我们可以在组件内通过 this.props.match.params 来访问到我们的路由参数对象,它的类型就是 Params

路由导航

我们可以使用两种方式来在页面中切换路由,react-router-dom 提供了一个 Link 组件,我们可以用它来替代传统的 a 标签,因为我们使用的 history 模式的 BrowserRouter,它其实是利用浏览器的 history.pushState 来实现的,它可以在浏览器添加历史状态,修改当前的 URL 而不会引起页面的刷新,Link 可以帮我们拦截用户的点击,然后完成 pushState 相关的操作,这样就可以在切换路由的同时保持页面的状态,而不是刷新页面。它的基本用法如下:

ts
import { Link } from "react-router-dom";

export default class Main extends React.Component {
  render() {
    return <Link to="/about">About</Link>;
  }
}

参数 to 就是要导航到的路由,需要注意,Link 标签只用来切换应用内的路由,如果你想要跳转到外部链接,例如 MDN,那你应该使用传统的 a 标签,如

ts
<a href="https://developer.mozilla.org/zh-CN/">MDN</a>

还有一种方式是通过 API 来进行导航,对于路由组件,react-router-dom 会通过 props 传入一些数据和 API,我们可以通过调用下面的 API 来进行路由导航

ts
this.props.history.push("/about");

需要注意的是,this.props.history 只在路由组件内部才可以使用,如果你想要在普通组件内部也能访问路由信息,需要使用 withRouter 来包裹一下,例如

ts
import * as React from "react";
import { Link, withRouter, RouteComponentProps } from "react-router-dom";

class Nav extends React.Component<RouteComponentProps> {
  componentDidMount() {
    console.log(this.props.history);
  }

  render() {
    return (
      <nav className="nav">
        <Link to="/" className="nav-item">
          Main
        </Link>
        <Link to="/about" className="nav-item">
          About
        </Link>
      </nav>
    );
  }
}

export default withRouter(Nav);

这样我们就可以访问路由信息了

react-router-dom 还提供了一个特殊的 Link 组件叫 NavLink,它可以让我们来创建一些导航元素,当前的路由如果和它的链接匹配的话,可以自动帮我们添加选中的状态,例如上面的 Nav 组件我们可以用 NavLink 来替换一下

ts
import * as React from "react";
import { NavLink, withRouter, RouteComponentProps } from "react-router-dom";

class Nav extends React.Component<RouteComponentProps> {
  render() {
    return (
      <nav className="nav">
        <NavLink to="/" className="nav-item" exact activeClassName="nav-active">
          Main
        </NavLink>
        <NavLink to="/about" className="nav-item" activeClassName="nav-active">
          About
        </NavLink>
      </nav>
    );
  }
}

export default withRouter(Nav);

当路由和 NavLink 中的 to 匹配时,会自动添加一个 nav-active 类名