react-router保姆式笔记(含V5、6两个版本)

1,801 阅读5分钟

「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战

本文主要讲怎么使用react-router,对各种场景下,V5、V6两个版本的实现方式

image.png

一、什么是路由

一个路由就是一个映射关系

key路径value可能是functioncomponent

二、路由分类

  • 后端路由(node)
    • valuefunction,用来处理客户端提交的请求
    • 注册路由:router.get(path, function(req, res))
    • 工作过程:当node接收到一个请求时,根据请求路径找到匹配路由,调用路由中的函数来处理请求,返回响应参数
  • 前端路由
    • valuecomponent,用于展示页面内容
    • 注册路由:<Route path="/home" element={<Home />} >
    • 工作过程:当浏览器的path变为home时,当前路由组件就会变成Home组件

三、路由的原理

npm包 history.js

  • history
    • push
    • replace
    • 前进
    • 后退
    • push与replace的区别
  • hash,锚点

四、react router实现

1、react-router-dom的理解

react的路由库分为三类

  • web react-router-dom
  • native
  • any 所以我们用react-router-dom包,作用如下
  • 专门用来实现一个SPA应用

版本,V6:目前最新版,与V5有较大改动,主要用来拥抱hooks,很多钩子,类式组件使用不了

2、react-router-dom相关API

内置组件

  • <BrowserRouter>
  • <HashRouter>
  • <Route>
  • <Redirect> V6已经废弃
  • <Link>
  • <NavLink>
  • <Switch> V6已经废弃

五、路由基本使用,Link、Route

1、导航区写Link标签

Link标签上有replace参数,默认 false,即跳转路由要用 push 还是 replace

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

编译为

<a href="/home">home</a>

2、展示区写Route标签进行路由匹配

  • V5通过component={Home}
  • V6通过element={<Home />,并需要包裹在Routes标签中
import Home from "./pages/home";
import About from "./pages/about";
// V5
<Route path="/home" component={Home} />
<Route path="/about" component={About} />

// V6
<Routes>
    <Route path="/home" element={<Home />} />
    <Route path="/about" element={<About />} />
</Routes>

3、APP最外层包一层<BrowserRouter>或者<HashRouter>

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";

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

六、路由组件&一般组件

  • 定义
    • 一般组件直接使用,如Home
    • 路由组件通过路由监听渲染
  • 文件夹位置不同
    • 一般组件放在components里面
    • 路由组件放在pages里面
  • 收到的props不同
    • 一般组件传啥收啥
    • 路由组件能收到外层Route组件传的props

七、NavLink

1、基本使用

NavLink使用,支持高亮颜色,高亮class名默认为active,支持自定义

  • V5通过activeClassName自定义选中态的类名
  • V6通过给className传入函数,依赖isActive参数确定
// V5
<NavLink className='link' activeClassName='nav-active' to="/home">
    home
</NavLink>
// V6
<NavLink className={({isActive}) => `link ${isActive ? 'nav-active' : ''}`} to="/home">
    home
</NavLink>

2、封装NavLink

标签体内容是一个特殊的标签属性,通过this.props.children可以获取标签体内容 封装为一个组件,避免NavLink高亮颜色重复写

// src/components/app-nav-link/index
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'

export default class AppNavLink extends Component {
    render () {
        return (
            <div>
                <NavLink
                    className={({ isActive }) => `link ${isActive ? 'nav-active' : ''}`}
                    {...this.props}
                >
                    {this.props.children}
                </NavLink>
            </div>
        )
}

使用AppNavLink

import AppNavLink from "./components/app-nav-link";

{
    [{ to: '/home', children: 'home' }, { to: '/about', children: 'about' }].map(nav =>
        <AppNavLink key={nav.to} {...nav} />
    )
}

八、Switch的使用

router V5有这个标签,V6已被重命名为<Routes> 单一匹配路由,通常情况下pathcomponent一一对应的关系,Switch可以提高路由匹配效率

九、多级路径刷新页面样式丢失问题

解决方案

  • public/index.html 中引入样式不写 .//
  • public/index.html 中引入样式不写 ./%PUBLIC_URL%
  • 使用HashRouter

十、精准匹配与模糊匹配

  • V5默认是模糊匹配,通过在Route配置加exact开启精准匹配
  • V6默认开启精准匹配,加/*开启模糊匹配

十一、重定向

一般写在所有路由的最下方,当所有路由无法匹配的时候,跳转到兜底的路由

  • V5通过<Redirect to="/home" />
  • V6已废除Redirect标签,通过<Route path="*" element={<Navigate to="/home" />} />
import { Route, Routes, Navigate, Redirect } from "react-router-dom";
// V5
<Routes className="route">
    <Route path="/home" component={Home} />
    <Route path="/about" component={About} />
    <Redirect to="/home" />
</Routes>

// V6
<Routes className="route">
    <Route path="/home" element={<Home />} />
    <Route path="/about" element={<About />} />
    <Route path="*" element={<Navigate to="/home" />} />
</Routes>

十二、嵌套路由

V5注册子路由需要写父路由的path值,V6的版本都不需要写/,只需要地址就行,也不需要你写前面的路径,只需要你写下个路径是啥就行

  • 路由的匹配是按照注册路由的顺序执行的
// V5

{/* 导航区 */}
{[
    { to: "/home", children: "home" },
    { to: "/about", children: "about" },
].map((nav) => (
    <AppNavLink key={nav.to} {...nav} />
))}

{/* 展示区 */}
<Switch className="route">
    <Route path="/home" component={Home} />
    <Route path="/about" component={About} />
    <Redirect to="/home" />
</Switch>

// pages/home/index.js
 <div style={{ display: 'flex' }}>
    <AppNavLink to='/home/message' children="message" />
    <AppNavLink to='/home/news' children="news" />
</div>

<Switch>
    <Route path="/home/message" component={HomeMessage} />
    <Route path="/home/news" component={HomeNews} />
</Switch>

// V6
// app.js
{/* 导航区 */}
{[
    { to: "/home", children: "home" },
    { to: "/about", children: "about" },
].map((nav) => (
    <AppNavLink key={nav.to} {...nav} />
))}

{/* 展示区 */}
<Routes className="route">
    <Route path="/home/*" element={<Home />} />
    <Route path="/about/*" element={<About />} />
    <Route path="*" element={<Navigate to="/home" />} />
</Routes>

// pages/home/index.js
 <div style={{ display: 'flex' }}>
    <AppNavLink to='message' children="message" />
    <AppNavLink to='news' children="news" />
</div>

<Routes>
    <Route path="message" element={<HomeMessage />} />
    <Route path="news" element={<HomeNews />} />
</Routes>

十三、路由向组件传递props

1、params传参

// V5
{/* 向组件传递params参数 */}
<Link to={`detail/${msg.id}/${msg.title}`}>{msg.title}</Link>

{/* 声明接收params参数 */}
<Route path="detail/:id/:title" component={HomeMessageDetail} />

{/* 接收params参数 */}
const {id, title} = this.props.match.params

2、search传参

urlencoded编码:类似key=value&key=value的编码方式,可用npm包url-parse处理,

// V5

{/* 向组件传递search参数 */}
<Link to={`detail?id=${msg.id}&title=${msg.title}`}>{msg.title}</Link>

{/* 声明接收search参数(无需声明,正常注册即可) */}
<Route path="detail" component={HomeMessageDetail} />

// 接收search参数
import qs from 'url-parse'
const {search} = this.props.location
const {id, title} = qs.parse(search)

3、state传参

刷新并不会更新,因为存在了history里面,但是清除浏览器缓存再刷新就会丢失

// V5

{/* 向组件传递state参数 */}
<Link to={{pathname: 'detail', state: {...msg}}}>{msg.title}</Link>

{/* 声明接收state参数(无需声明,正常注册即可) */}
<Route path="detail" component={HomeMessageDetail} />

// 接收state参数
const {id, title} = this.props.location.state

十四、编程式路由导航

  • V5通过this.props.history上的pushreplacegobackforward方法
  • V6通过useNavigatenavigate
    • naviaget(to)默认就是history.push
    • naviaget(to, { replace: true })就是 history.replace
    • naviaget(to: number)就是、history.go
// V5
this.props.history.push('XX')
this.props.history.replace('XX')
this.props.history.go(2)
this.props.history.back()
this.props.history.forward()

// V6
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
<button onClick={() => navigate(`detail/${news.id}/${news.title}`)}>push</button>
<button onClick={() => navigate(`detail/${news.id}/${news.title}`, {replace: true})}>replace</button>

十五、withRouter

withRouter可以加工一般组件,让一般组件具备路由组件所特有的API,返回的是一个新组件

V6已废弃,V5中一般组件(非路由组件)想要用路由的API,比如this.props.history.push(),需要用withRouter包一层

十六、BrowserRouter & HashRouter

  • 原理不一样
    • BrowserRouter使用的是H5的history API,不兼容IE9以下版本
    • HashRouter使用的是URL的哈希值
  • url表现形式不一样
    • BrowserRouter的路径中没有#
    • HashRouter的路径中包含#
  • 刷新后对路由state参数的影响
    • BrowserRouter没有任何影响,因为state存在history对象中
    • HashRouter刷新后会导致路由state参数的丢失

十七、V5 & V6总结

  • <Switch>重命名为<Routes>
  • Routerendercomponent 改为 element
  • 嵌套路由变得更简单
  • tonavigatepath 不以 / 开头,都是相对路径
  • 用 Navigate 代替 Redirect
  • useNavigate代替useHistory
  • 大小减少:从20kb8kb

十八、路由懒加载

借助react提供的lazySuspense实现

import React, { Component, lazy, Suspense } from "react";

// 路由懒加载
const Home = lazy(() => import("./pages/home"));
const About = lazy(() => import("./pages/about"));

<Suspense fallback={<div>Loading...</div>}>
  <Routes className="route">
    <Route path="home/*" element={<Home />} />
    <Route path="about/*" element={<About />} />
    <Route path="*" element={<Navigate to="home" />} />
  </Routes>
</Suspense>

参考

juejin.cn/post/684490… juejin.cn/post/702541… juejin.cn/post/704440…