重学 React 之路由

1,381 阅读6分钟

重学 React 之基础认知
重学 React 之三大属性
重学 React 之生命周期

在我当初第一次学 react 时,我一直没弄明白 react 路由的使用,这次我终于自己理清楚了。本篇将介绍常用路由组件的作用、路由组件传参以及编程式导航。

长话短说,开发 react web 应用,使用的路由库是 react-router 中的 web 版本,即 react-router-dom 。注意如果是用 create-react-app 脚手架创建的项目,需要单独安装 react-router-dom

react-router-dom 中暴露了一些实现路由切换的组件,也是我们要学习的部分。

本文 React 用例版本:v17.0.2

Link,Route,BrowserRouter

使用这三个组件就可以完成路由切换,先看例子再介绍:

import {Link, Route, BrowserRouter} from 'react-router-dom'

<BrowserRouter>
  <Link to="/home">home</Link>
  <Link to="/about">about</Link>

  <Route path='/home'> <Home/> </Route>
  <Route path='/about'> <About/> </Route>
</BrowserRouter>

Link 组件,相当于 a 标签,其实最终在页面上也是渲染成 a 标签,它的作用就是切换路由的,当点击时,会激活对应的路由路径,它的 to 属性就是标记路由路径的。用过 vue 的一定熟悉 <router-link>,跟 Link类似。

Route 组件,用于注册路由,path 属性匹配当前路由路径,,component 属性传入当前路径匹配的组件,当 path 匹配到时,就渲染 component 传入的组件。

也可以将要渲染的组件,直接写在组件标签中,如下:

<Route path='/home'> <Home/> </Route>
<Route path='/about'> <About/> </Route>

添加 exact 开启严格匹配模式,即 path 值需要跟 Link 中的 to 的值一致时才匹配。

BrowserRouter 组件,不知道怎么解释比较好,可以理解为要在路由应用的最外层,使用 BrowserRouter 包裹。

其实更准确一点应该是将互相映射的 LinkRoute (不知道这么表达对不对)包裹在同一个 BrowserRouter 下,也就是说如果像下面这样分开包裹就是错的:

<BrowserRouter>
  <Link to="/home">home</Link>
  <Link to="/about">about</Link>
</BrowserRouter>

<BrowserRouter>
  <Route path='/home' component={Home}></Route>
  <Route path='/about' component={About}></Route>
</BrowserRouter>

分开使用 BrowserRouter包裹,是无法匹配到对应组件的。有时为了方便,可以将 BrowserRouter 组件应用到入口文件中,如下:

ReactDOM.render(
  <BrowserRouter>
      <App />
  </BrowserRouter>,
  document.getElementById('root')
);

这样在你的组件中就不需要再引入 BrowserRouter 了,可以随意位置写 LinkRoute 了。

实际开发中,也不会像开头的例子一样,将它们全写在一起,这样不灵活,只需记住,互相匹配的 Link 和 Route 在同一个 BrowserRouter 即可。

HashRouter

作用与 BrowserRouter 一样,只是从 history 式路由,变成 hash 式路由。

跟 vue-router 中一样的,一般 SPA 应用都有这两种路由模式,原理这里就不再展开说明,社区有很多相关文章。

NavLink

作用与 Link 一样,只是在路由激活时,会在渲染后的 a 标签上加上 active 类名 ,这样的好处就是可以定义路由激活时的样式,只需要对 active 类定义样式。

同时可以修改激活时的类名,使用 activeClassName 属性自定义激活类名:

<NavLink activeClassName="selected" to="/about">about</NavLink>
<NavLink activeClassName="selected" to="/home">home</NavLink>

最终渲染的 dom 元素上会带上这个 class 类名

image-20210831142543538.png

Switch

Switch 组件用来包裹 Route 的,它可以提高路由匹配效率,Route 匹配路由时,即使匹配到了还会继续往后面匹配,例如:

<Route path='/home' component={Home}></Route>
<Route path='/home' component={Test}></Route>

上面代码中两个 Route 匹配的 path 都是 /home,最终 HomeTest 组件都会渲染到页面。

如果有很多个 Route,也会一直往后面匹配直至结束,这显然影响效率。

如果要避免这种情况,就可以使用 Switch 包裹,这样就是单一路由匹配,匹配到就结束。

<Switch>
  <Route path='/home' component={Home}></Route>
  <Route path='/home' component={Test}></Route>
</Switch>

此时,Test 组件就不会再匹配。

Redirect

重定向组件,可以在匹配规则后重定向到指定路由。

当用在 Switch 中时,可以将 Redirect 放到最后面用于“兜底”作用,即上面所有的路由都没匹配到时,重定向,例:

<Switch>
  <Route path='/home' component={Home}></Route>
  <Route path='/about' component={About}></Route>
  // 当上面两个都没匹配到时,重定向到 /home
  <Redirect to="/home" />
</Switch>

当直接用在组件中时,组件渲染时就会触发重定向:

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

export default class Test extends Component {
    render() {
        return (
            <div>
                Test 组件,当我渲染时会重定向到 /about
                <Redirect to='/about' />
            </div>
        )
    }
}

向路由组件传递参数

向路由组件传递参数有三种常见方法:

1. 传递 params 参数

参数值直接拼接在路由链接上,例如要向文章页面传递文章 id 参数:

// 123 是要传递的文章id
<Link to='/article/123'></Link>

路由组件需要声明接收id参数:

<Route path='/article/:id' component={Article}/>

然后在组件内部获取参数:

// 获取传递过来的 params 参数
this.props.match.params // {id: '123'}

2. 传递 search 参数

参数值也是拼接在路由链接上,但是需要用query的方式,即加 ? 前缀,还是上面的例子,search 方式:

<Link to='/article?id=123'></Link>

路由组件不需要声明接收 id 参数:

<Route path='/article' component={Article} />

组件内部获取参数:

// 获取 search 参数
this.props.location.search // ?id=123

获取到的是参数字符串,需要自行格式化,可以 借助 query-string 库进行参数格式化:

import querystring from 'query-string'
querystring.parse(this.props.location.search) // {id:'123'}

3. 传递 state 参数

参数值需要已对象的方式传递:

<Link to={{pathname:'/article',state:{id:123}}}></Link>

路由组件也不需要声明接收 id 参数:

<Route path='/article' component={Article} />

组件内获取参数:

// 获取 state 参数
this.props.location.state // {id: '123'}

注:此方式刷新页面时,如果启用的是 BrowserRouter,状态不会消失,如果用的是 HashRouter,状态会消失。

编程式路由导航

React 中使用编程式导航,主要利用路由组件中的 props属性的 history 对象中的 pushreplacegogoBackgoForward等 api。

这样使用:this.props.history.push('/home'),使用很简单,这里就不展开说明了。

withRouter

只有路由组件的 props 上才有路由参数,才能进行编程式导航,非路由组件是不能直接进行编程式导航的。

所谓非路由组件,即不是通过 Route 切换的组件,比如 react 应用中的 App.js 这个组件,一般是首页入口,是通过浏览器输入地址打开的,而不是通过路由跳转的,它就不能直接使用编程式导航,它的 this.props 为空。

withRouter 的作用就是将路由参数 historylocationmatch 三个对象传入组件的 props 对象上,它的使用方法也很简单,直接包裹组件即可,例子:

import React, { Component } from "react";
import { Route, withRouter } from "react-router-dom";
import Message from "./pages/Message";
import Home from "./pages/Home";
class App extends Component {
  render() {
    console.log(this.props); // 只有被 withRouter 包裹时,props 上才有路由参数
    return (
      <div className="App">
        <Route path="/message" component={Message}></Route>
        <Route path="/home" component={Home}></Route>
      </div>
    );
  }
}

export default withRouter(App); // 直接在这里包裹即可

结尾

如有错误,欢迎指出。