【React】路由

【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表现形式不一样

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

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

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

  • 两者一般使用在包裹所有使用路由的地方,一般在index.js的App
javascript 复制代码
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
  • 一个特殊版本的 Link,当它与当前 URL 匹配时,为其渲染元素添加样式属性。
  • v5版本中支持 activeClassName/activeStyle
  • v6 中 不再支持 activeClassName/activeStyle,而是通过 className 接收「回调函数」或「静态类名」,结合 isActive 状态控制激活样式。
javascript 复制代码
 <NavLink   className={({ isActive }) => isActive ? 'newitem nav-link' : 'nav-link'} to="/nev">nev</NavLink>
javascript 复制代码
.newitem{
  color: aqua;
}
  • 新建MyNavLink组件
javascript 复制代码
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
  • 使用
javascript 复制代码
        <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的时候,两者都会展示
javascript 复制代码
<Switch>
 <Route path="/main" element={<Main />} />
 <Route path="/nev" element={<Nev />} />
 <Route path="/nev" element={<Main />} />
</Switch>

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

javascript 复制代码
<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。
多级路径页面刷新的样式丢失问题
路由的模糊匹配和精准匹配(V5/V6区别)

v5版本下

  • 默认模糊匹配,顺序要一致,输入的路径必须包含匹配的路径
  • 精准匹配模式,Route上exact={true}开启精准匹配
javascript 复制代码
 <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 按「路径精准度」匹配(而非定义顺序),更精准的路径优先。
javascript 复制代码
// 所以下面的/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版本下
javascript 复制代码
<Route exact={true} path="/main" element={<Main />} />
<Route path="/nev" element={<Nev />} />
<Redirect to="/main"/>
V6版本下
javascript 复制代码
<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)
父组件
javascript 复制代码
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
子组件
javascript 复制代码
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
三级路由
javascript 复制代码
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)
javascript 复制代码
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
javascript 复制代码
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参数
javascript 复制代码
{/* 向路由组件传递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
javascript 复制代码
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参数
javascript 复制代码
 {/* 向路由组件传递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
javascript 复制代码
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属性(替换当前路由)
编程式导航
javascript 复制代码
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
前进和后退
javascript 复制代码
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
javascript 复制代码
// 路由组件(直接被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>
  );
};
解决方案
javascript 复制代码
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(推荐):

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

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

javascript 复制代码
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);
相关推荐
Justin3go14 小时前
HUNT0 上线了——尽早发布,尽早发现
前端·后端·程序员
怕浪猫15 小时前
第一章 JSX 增强特性与函数组件入门
前端·javascript·react.js
铅笔侠_小龙虾15 小时前
Emmet 常用用法指南
前端·vue
钦拆大仁15 小时前
跨站脚本攻击XSS
前端·xss
VX:Fegn089516 小时前
计算机毕业设计|基于springboot + vue校园社团管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·课程设计
ChangYan.17 小时前
直接下载源码但是执行npm run compile后报错
前端·npm·node.js
skywalk816317 小时前
在 FreeBSD 上可以使用的虚拟主机(Web‑Hosting)面板
前端·主机·webmin
ohyeah18 小时前
深入理解 React 中的 useRef:不只是获取 DOM 元素
前端·react.js
MoXinXueWEB18 小时前
前端页面获取不到url上参数值
前端
低保和光头哪个先来19 小时前
场景6:对浏览器内核的理解
开发语言·前端·javascript·vue.js·前端框架