【React】路由 - 指南

SPA的理解
  • 单页Web应用
  • 整个页面只有一个完整的页面,就一个.html
  • 点击页面中的链接不会刷新页面只会做页面的局部更新
  • 数据都需要ajax请求实现,并在前端展示
路由
  • 一个路由就是一个映射关系(key:value)
  • key为路径,value是function或components

后端路由

  • 当value是function,一般是后端用以处理客户端提交的请求,当node接收到一个请求的时候,根据请求路径找到匹配的路由,调用路由的函数处理请求,返回响应数据

前端路由

  • 当value是components,前端用于展示页面内容,游览器地址的变化成/test就是当前路由组件变为test组件
react-router的理解
  • react的插件库
  • 专门用来实现一个spa应用
  • 下面又有三个用途web,native(原生),any(任何)
react-router-dom(用于web)
  • 安装:npm i react-router-dom

  • BrowserRouter 或者HashRouter 包裹,鼓励 配合使用

  • Link 标签导航

  • 内容route标签展示

在这里插入图片描述

BrowserRouter和HashRouter

1,底层原理不一致

  • BrowserRouter使用的是h5的history API。不兼容IE9及以下版本
  • HashRouter 使用的是URL的哈希值

2,url表现形式不一样

  • BrowserRouter的路径中没有#,类似http://localhost:3000/tab2/nev1
  • HashRouter的路径中包含#,类似http://localhost:3000/#/tab2/nev1

3,刷新后对路由state参数的影响

  • BrowserRouter没有任何影响,因为state保存在history对象中
  • HashRouter刷新后导致state参数的丢失

备注:HashRouter可以用于解决一些路径错误相关的问题

  • 两者一般使用在包裹所有使用路由的地方,一般在index.js的App
  • 在这里插入图片描述
Link 和 Route
import React from 'react'
import Main from './components/Main'
import Nev from './components/Nev'
import { Link, BrowserRouter, Route, Routes } from 'react-router-dom'
const App = () => {
return (
<BrowserRouter>
  <div>
    {/* 在react中靠路由链接实现切换组件 */}
    <div>
      <Link to="/main">main</Link>
        <Link to="/nev">nev</Link>
          </div>
            <div>
              {/* v6 中 <Route> 不再支持 component,必须改用 element 并传入组件实例(<Main /> 而非 Main) */}
              <Routes>
                <Route path="/main" element={<Main />} />
                  <Route path="/nev" element={<Nev />} />
                    </Routes>
                      </div>
                        </div>
                          </BrowserRouter>
                            )
                            }
                            export default App
Navlink
  • 一个特殊版本的 Link,当它与当前 URL 匹配时,为其渲染元素添加样式属性。
  • v5版本中支持 activeClassName/activeStyle
  • v6 中 不再支持 activeClassName/activeStyle,而是通过 className 接收「回调函数」或「静态类名」,结合 isActive 状态控制激活样式。
<NavLink   className={({ isActive }) => isActive ? 'newitem nav-link' : 'nav-link'} to="/nev">nev</NavLink>
.newitem{
color: aqua;
}

在这里插入图片描述

Navlink的封装
  • 新建MyNavLink组件
import React from 'react'
import { NavLink } from 'react-router-dom'
const MyNavLink = (props) => {
const { to } = props
//实际上传递了children,children就是title
return (
<NavLink
className={({ isActive }) => (isActive ? 'newitem nav-link' : 'nav-link')}
to={to}
{...props}
>
</NavLink>
  )
  }
  export default MyNavLink
  • 使用
<div>
  <NavLink   className={({ isActive }) => isActive ? 'newitem nav-link' : 'nav-link'} to="/main">main</NavLink>
    <NavLink   className={({ isActive }) => isActive ? 'newitem nav-link' : 'nav-link'}to="/nev">nev</NavLink>
      <MyNavLink to="tab1"></MyNavLink>
        //实际上传递了children,children就是title
        <MyNavLink to="tab2">tab2</MyNavLink>
          </div>
            <div>

在这里插入图片描述

Switch和Routes(V5/V6区别)
  • 通常情况下,path和compent是一一对应的
  • Switch可以提高路由匹配效率,单一匹配

v5版本下,必须通过添加Switch来实现,同一路径的多个 只会渲染第一个匹配的

  • 不然,切换到nev的时候,两者都会展示
<Switch>
  <Route path="/main" element={<Main />} />
    <Route path="/nev" element={<Nev />} />
      <Route path="/nev" element={<Main />} />
        </Switch>

V6下,React Router v6 的 组件会按「最佳匹配 + 首次匹配优先」规则渲染路由,同一路径的多个 只会渲染第一个匹配的。

<Routes>
  <Route path="/main" element={<Main />} />
    <Route path="/nev" element={<Nev />} />
      <Route path="/nev" element={<Main />} />
        </Routes>
路由组件和一般组件

写法不同,

  • 一般组件< Demo/ >
  • 路由组件, <Route path=“/main” element={} />

存储位置不同

  • 一般组件放components
  • 路由组件一般放pages

接收到的props不同

  • 一般组件,写组件标签的时候传递啥,接收啥
  • 路由组件,接收三个固定属性,history,location,match
    v5:路由组件会自动接收 history/location/match 等 props;
    v6:路由组件默认接收空 props,需通过 useNavigate/useLocation/useParams 等 Hooks 获取路由相关信息,或手动传递 props。
多级路径页面刷新的样式丢失问题
  • 当路径增加前缀/pages的时候
  • 根页面的页面静态资源丢失,原因:请求路径http://localhost:3000/css/bootstrap.css变成http://localhost:3000/pages/css/bootstrap.css,所以请求不到这个样式了
  • -解决方法:
  • 1,采用绝对路径,类似./css/bootstrap.css变成/css/bootstrap.css
  • 2,采用%PUBLIC_URL%,<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css" />,只适用public文件下的
  • 3,采用hash路由模式,HashRouter 替换BrowserRouter,/#/后的都是前端资源,刷新请求的时候路径不会带上/pages/,但不建议使用这个方式
    在这里插入图片描述
路由的模糊匹配和精准匹配(V5/V6区别)

v5版本下

  • 默认模糊匹配,顺序要一致,输入的路径必须包含匹配的路径
  • 精准匹配模式,Route上exact={true}开启精准匹配
<Link to="/main/a/b">tab1</Link>
  <Route exact path="/main" element={<Main />}

v6版本下

  • React Router v6 的 默认采用「精确匹配」规则,
  • v6 彻底抛弃了 v5 的「模糊匹配」,默认规则:
  • 精确匹配:只有 URL 路径与 完全一致时才匹配(如 /nev 只匹配 /nev,不匹配 /nev/a/b);
  • 子路径匹配:需通过「嵌套路由 + 」或「通配符 *」实现;
  • 顺序无关:v6 按「路径精准度」匹配(而非定义顺序),更精准的路径优先。
// 所以下面的/main/a/b无法匹配上下面的main
<div>
  <div>
    <Link to="/main/a/b">tab1</Link>
      <MyNavLink to="/nev">tab2</MyNavLink>
        </div>
          <div>
            <Routes>
              <Route exact={true} path="/main" element={<Main />} />
                <Route path="/nev" element={<Nev />} />
                  </Routes>
                    </div>
                      </div>
路由重定向Redirect和Navigate
  • 一般写在所有路由注册的最下方,当所有路由无法匹配的时候,跳转到Redirect指定的路由
V5版本下
<Route exact={true} path="/main" element={<Main />} />
  <Route path="/nev" element={<Nev />} />
    <Redirect to="/main"/>
V6版本下
<Routes>
  <Route exact={true} path="/main" element={<Main />} />
    <Route path="/nev" element={<Nev />} />
      <Route path="*" element={<Navigate to="/main" replace />} />
        </Routes>
嵌套路由
  • 父路由(Tab2)路径:/tab2/*
  • 子路由(Nev2)路径:nev2/(继承后为 /tab2/nev2/
  • 三级路由(Detail)路径:detail(继承后为 /tab2/nev2/detail)
父组件
import React from 'react'
import { Link, Route, Routes, Navigate } from 'react-router-dom'
import Tab1 from './components/tab1'
import Tab2 from './components/tab2'
const App = () => {
return (
<div>
  {/* 编写路由链接 */}
  <Link to="/tab1">tab1</Link>
    <Link to="/tab2">tab2</Link>
      <div>
        {/* 注册路由 */}
        <Routes>
          <Route path="/tab1" element={<Tab1 />}></Route>
            <Route path="/tab2/*" element={<Tab2 />}></Route>
              <Route path="*" element={<Navigate to="/tab1" replace />} />
                </Routes>
                  </div>
                    </div>
                      )
                      }
                      export default App
子组件
import React from 'react'
import Nev1 from './nev1'
import Nev2 from './nev2'
import {
Link,
NavLink,
BrowserRouter,
Route,
Routes,
Navigate,
} from 'react-router-dom'
const index = () => {
return (
<div>
  <div>tab2内容</div>
    {/* (即 /tab2/nev1/nev2)问题是 React Router 相对路径的核心特性导致的 */}
    {/* 方案 1:使用绝对路径(最稳妥,避免路径叠加) */}
    {/* 方案 2:使用 ../ 回到上一级(相对路径修正) */}
    {/* 方案 3:使用 useNavigate 编程式导航(灵活控制) */}
    <Link to="/tab2/nev1" style={{ marginRight: '10px' }}>
      nev1
      </Link>
        <Link to="/tab2/nev2">nev2</Link>
          <Routes>
            {/* 首次进入 tab2 时默认显示 nev1 */}
            <Route path="/" element={<Navigate to="nev1" replace />} />
              <Route path="nev1" element={<Nev1 />}></Route>
                <Route path="nev2/*" element={<Nev2 />}></Route>
                  </Routes>
                    </div>
                      )
                      }
                      export default index
三级路由
import { useState } from 'react'
import { Link, Route, Routes, Navigate } from 'react-router-dom'
import Detail from './detail'
const Nev2 = () => {
const [todos, setTodos] = useState([
{ id: 1, name: '乞力马扎罗', check: 1, route: '' },
{ id: 2, name: '打代码', check: 0, route: '' },
{ id: 3, name: '搞钱', check: 0, route: '' },
])
return (
<div>
  nev2子级路由页面
  {/* 编写路由链接 */}
  {todos.map((item) => {
  return (
  <li key={item.id}>
    <Link to="/tab2/nev2/detail" >
      {item.name}
      </Link>
        </li>
          )
          })}
          {/* 注册路由 */}
          <Routes>
            <Route path="detail" element={<Detail />}></Route>
              </Routes>
                </div>
                  )
                  }
                  export default Nev2

在这里插入图片描述

路由向组件传递params参数(V6)
import { useState } from 'react'
import { Link, Route, Routes, Navigate } from 'react-router-dom'
import Detail from './detail'
const Nev2 = () => {
const [todos, setTodos] = useState([
{ id: 1, name: '乞力马扎罗', check: 1, route: '' },
{ id: 2, name: '打代码', check: 0, route: '' },
{ id: 3, name: '搞钱', check: 0, route: '' },
])
return (
<div>
  nev2子级路由页面
  {/* 编写路由链接 */}
  {/* 1,向路由组件传递params参数 */}
  {todos.map((item) => {
  return (
  <li key={item.id}>
    <Link to={`/tab2/nev2/detail/${item.id}/${item.name}`} >
      {item.name}
      </Link>
        </li>
          )
          })}
          {/* 注册路由 */}
          {/* 2,声明接收params参数 */}
          <Routes>
            <Route path="detail/:id/:title" element={<Detail />}></Route>
              </Routes>
                </div>
                  )
                  }
                  export default Nev2
  • detail.jsx
import React from 'react'
// 1. 引入 useParams 钩子
import { useParams } from 'react-router-dom'
function Detail() {
// 2. 调用 useParams 获取路由参数(与路由中声明的 :id/:title 对应)
const params = useParams()
// 3. 解构出 id 和 title(注意:参数名要和路由中声明的一致)
const { id, title } = params
// 打印参数(可在控制台查看)
console.log('接收的参数:', id, title)
{
/* nev路由传参-->详情页面接收参数 */
}
return <div>nev路由传参详情页面</div>
  }
  export default Detail

在这里插入图片描述

路由向组件传递search参数
{/* 向路由组件传递search参数 */}
<Link to={`/tab2/nev2/detail2/?id=${item.id}&title=${item.name}`}>
{item.name}详情2
</Link>
  {/* 注册路由 */}
  <Routes>
    {/* search参数无需声明接收,正常注册路由即可*/}
    <Route path="detail2" element={<Detail2 />}></Route>
      </Routes>
  • detail2.jsx
import React from 'react'
// 引入 useSearchParams 钩子
import { useSearchParams } from 'react-router-dom'
function Detail(props) {
// useSearchParams 返回数组:[查询参数对象, 修改参数的方法]
const [searchParams] = useSearchParams()
// 读取 id 和 title 参数(注意:获取的值是字符串类型)
const id = searchParams.get('id')
const title = searchParams.get('title')
console.log('Search 参数:', { id, title })
{
/* nev路由传参-->详情页面接收参数 */
}
return <div>nev路由传参详情页面2</div>
  }
  export default Detail

两者区别

在这里插入图片描述

路由向组件传递state参数
{/* 向路由组件传递state参数 */}
<Link
to="/tab2/nev2/detail3"
state={{
id: item.id,
name: item.name,
check: item.check,
}}
>
{item.name}详情3
</Link>
  <Routes>
    {/* state参数无需声明接收,正常注册路由即可*/}
    <Route path="detail3" element={<Detail3 />}></Route>
      </Routes>
  • detail3.jsx
import React from 'react'
// 引入 useLocation  钩子
import { useLocation } from 'react-router-dom'
function Detail() {
//  接收 state 参数
const location = useLocation()
const state = location.state || {} // 防止无参数时报错
console.log('state 参数:',state)
{
/* nev路由传参-->详情页面接收参数 */
}
return <div>nev路由传参详情页面2</div>
  }
  export default Detail
传递各种类型的 接收参数(V5版本)

在这里插入图片描述

replace属性(替换当前路由)
编程式导航
import React from 'react'
import Nev1 from './nev1'
import Nev2 from './nev2'
import { Route, Routes,Navigate, useNavigate } from 'react-router-dom'
const Index = () => {
const navigate = useNavigate()
function nextPage(targetPath) {
// v5
// props.history.push(targetPath)
// v6
navigate(targetPath)
}
function nextPagereplace(targetPath) {
// v5
// props.history.replace(targetPath)
// v6
navigate(targetPath, { replace: true })
}
return (
<div>
  <div>tab2内容</div>
    <div>
      <button onClick={() => nextPage('/tab2/nev1')}>跳转到nev1</button>
        <button onClick={() => nextPagereplace('/tab2/nev2')}>跳转到nev2</button>
          </div>
            <Routes>
              <Route path="/" element={<Navigate to="nev1" replace />} />
                <Route path="nev1" element={<Nev1 />}></Route>
                  <Route path="nev2/*" element={<Nev2 />}></Route>
                    </Routes>
                      </div>
                        )
                        }
                        export default Index

在这里插入图片描述

前进和后退
import React from 'react'
import { Link, Route, Routes, Navigate, useNavigate } from 'react-router-dom'
import './App.css'
const App = () => {
const navigate = useNavigate()
function forward() {
navigate(1)
}
function back() {
navigate(-1)
}
return (
<div className="pageStyle">
  <button onClick={back}>回退</button>
    <button onClick={forward}>前进</button>
      </div>
        )
        }
        export default App
onClick的两种点击写法
  • ✅ 无参函数推荐:onClick={back}(简洁 + 高性能);
  • ✅ 有参函数推荐:onClick={() => nextPage(参数)}(灵活,日常开发首选);
  • 优化点:如果箭头函数导致性能问题(如列表高频渲染),可改用 useCallback 缓存函数:
  • ❌ 错误写法:onClick={back()}
    加括号会导致组件渲染时立即执行 back(比如页面一加载就后退),而非点击时执行;
    在这里插入图片描述
withRouter(V5)
  • 是 React Router v4/v5 时代的核心高阶组件(HOC),核心作用是给非路由组件注入路由相关的 props
// 路由组件(直接被Route渲染,自带路由props)
const Home = (props) => {
console.log(props.history); // 能拿到
return <div>首页</div>;
  };
  // 普通组件(非Route渲染,无路由props)
  const NavButton = (props) => {
  console.log(props.history); // undefined
  // 想点击跳转,但没有history.push方法
  return <button onClick={() => props.history.push('/home')}>跳转首页</button>;
    };
    // 页面中使用
    const App = () => {
    return (
    <Router>
      <Route path="/home" component={Home} />
        {/* NavButton是普通组件,无路由props */}
        <NavButton />
          </Router>
            );
            };
解决方案
import { withRouter } from 'react-router-dom';
// 用withRouter包裹普通组件
const NavButton = withRouter((props) => {
console.log(props.history); // 能拿到了
return <button onClick={() => props.history.push('/home')}>跳转首页</button>;
  });
V6下移除了 withRouter

函数组件:直接使用 useNavigate/useLocation/useParams 等 Hooks(推荐):

import { useNavigate } from 'react-router-dom';
const NavButton = () => {
const navigate = useNavigate();
return <button onClick={() => navigate('/home')}>跳转首页</button>;
  };

类组件:通过「函数组件包裹 + 传 props」的方式适配

import { useNavigate, useLocation } from 'react-router-dom';
// 封装HOC兼容类组件
const withRouter = (Component) => {
return (props) => {
const navigate = useNavigate();
const location = useLocation();
return <Component {...props} navigate={navigate} location={location} />;
  };
  };
  // 类组件使用
  class MyClassComponent extends React.Component {
  render() {
  return <div onClick={() => this.props.navigate('/home')}>跳转</div>;
    }
    }
    export default withRouter(MyClassComponent);
posted @ 2026-01-20 11:52  clnchanpin  阅读(3)  评论(0)    收藏  举报