React 路由的实现
一、前置核心认知
- React 是一个「单页面应用(SPA)」的核心库,SPA 的特点:页面只有一个 HTML 文件,页面切换时不会重新请求/刷新整个页面,只是页面局部内容更新。
- React 路由的本质:通过监听浏览器地址栏的路径变化,匹配对应的 React 组件,然后把匹配到的组件渲染到页面指定位置,这就是 React 路由的核心实现逻辑。
- 路由分类:
- 前端路由:就是
react-router-dom实现的路由,只做「路径-组件」的匹配渲染,不会请求后端,是 React 项目的核心路由方式; - 后端路由:根据前端请求的 URL 路径,返回对应的 HTML/数据,和 React 路由无关。
- 前端路由:就是
二、第一步:安装依赖
React 路由的核心库是 react-router-dom,专门用于浏览器环境的 React 项目,执行如下命令安装(最新版默认就是 v6):
bash
npm install react-router-dom
# 或者 yarn
yarn add react-router-dom
三、React Router v6 核心基础用法(最常用,必掌握)
核心原则
react-router-dom v6 中,所有路由相关的组件,都必须包裹在 BrowserRouter /HashRouter组件内部 才能生效,否则会报错。
方式1:全局根入口包裹(src/main.jsx / src/index.js)
这是项目开发的标准写法,只需要在整个应用的根组件外层包裹一次 BrowserRouter,整个项目的所有组件都能使用路由功能,一劳永逸。
jsx
// src/main.jsx (React18 入口文件)
import React from 'react'
import ReactDOM from 'react-dom/client'
// 1. 导入核心路由容器 BrowserRouter
import { BrowserRouter } from 'react-router-dom'
import App from './App'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
{/* 2. 包裹根组件 App,全局生效 */}
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
)
四、核心路由组件
React Router v6 精简了组件,核心就 4 个最常用的路由组件,没有多余的API,全部掌握就够用99%的场景:
1. <Routes> 路由容器组件
- 作用:路由规则的「容器」,专门用来包裹所有的
<Route>组件,是路由规则的父容器。 - 核心特性:v6 新增的组件,替代了 v5 的
<Switch>,会自动匹配「最佳路由规则」,匹配成功后就停止匹配(不会出现多个路由同时渲染的问题),性能更高。 - 语法:必须包裹
<Route>,且只能直接包裹<Route>。
2. <Route> 路由规则匹配组件
- 作用:核心路由规则,实现「路径(path) → 组件(element)」的映射关系。
- 核心属性:
path:匹配的浏览器地址栏路径(如/home、/about);element:路径匹配成功后,要渲染的 React 组件(必须是<组件 />格式,不能只写组件名);
- 特殊规则:
path="*"表示「匹配所有未定义的路径」,用于做 404页面 匹配。
3. <Link> 路由导航组件
- 作用:实现路由页面的「跳转导航」,等价于 HTML 中的
<a href="xxx">标签。 - 核心优势:
<a>标签跳转页面会刷新整个页面(不符合SPA特性),<Link>跳转只会局部更新组件,不刷新页面,完美契合SPA。 - 语法:
to="匹配的路由path",和<Route path="xxx">对应。
4. <Outlet> 嵌套路由出口组件
- 作用:专门用于 嵌套路由 场景,是「子路由组件」的渲染占位符。
- 通俗理解:父路由匹配的组件中,写一个
<Outlet>,那么子路由匹配的组件,就会渲染到这个<Outlet>的位置。
五、完整代码示例(基础路由 + 404 兜底)
步骤1:准备2个页面组件(示例)
jsx
// src/pages/Home.jsx
const Home = () => {
return <div>我是首页 - Home 组件</div>
}
export default Home
// src/pages/About.jsx
const About = () => {
return <div>我是关于页 - About 组件</div>
}
export default About
// src/pages/NotFound.jsx (404页面)
const NotFound = () => {
return <div>404 页面 - 您访问的路径不存在!</div>
}
export default NotFound
步骤2:在App.jsx中配置路由规则+导航
jsx
// src/App.jsx
import { Routes, Route, Link } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import NotFound from './pages/NotFound'
function App() {
return (
<div className="App">
{/* 一、路由导航:Link组件 */}
<div style={{margin: '20px'}}>
<Link to="/" style={{marginRight: '20px'}}>首页</Link>
<Link to="/about">关于我们</Link>
</div>
{/* 二、路由规则:Routes + Route 组件 */}
<Routes>
{/* 匹配首页路径,渲染Home组件 */}
<Route path="/" element={<Home />} />
{/* 匹配关于页路径,渲染About组件 */}
<Route path="/about" element={<About />} />
{/* 404兜底:匹配所有未定义的路径 */}
<Route path="*" element={<NotFound />} />
</Routes>
</div>
)
}
export default App
效果测试
- 打开页面,地址栏是
http://localhost:5173/→ 渲染 Home 组件; - 点击「关于我们」→ 地址栏变成
http://localhost:5173/about→ 渲染 About 组件; - 手动修改地址栏为
http://localhost:5173/xxx→ 渲染 NotFound 404组件; - 所有跳转都不会刷新页面,完美实现SPA!
六、进阶必备:嵌套路由
什么是嵌套路由?
就是「路由套路由」,比如:
- 父路由:
/user→ 渲染 User 父组件; - 子路由:
/user/profile→ 渲染 Profile 子组件; - 子路由:
/user/setting→ 渲染 Setting 子组件; 父组件中会固定展示一些内容(如用户侧边栏),子组件的内容会根据路径变化,渲染到父组件的指定位置。
嵌套路由完整实现
步骤1:准备嵌套的页面组件
jsx
// src/pages/User.jsx (父路由组件)
import { Link, Outlet } from 'react-router-dom'
const User = () => {
return (
<div>
<h2>我是用户中心(父组件)</h2>
{/* 子路由导航 */}
<div style={{margin: '10px 0'}}>
<Link to="/user/profile" style={{marginRight: '10px'}}>个人资料</Link>
<Link to="/user/setting">账户设置</Link>
</div>
{/* 核心:子路由组件的渲染占位符 */}
<Outlet />
</div>
)
}
export default User
// src/pages/Profile.jsx (子路由组件)
const Profile = () => {
return <div>个人资料:姓名、年龄、头像...</div>
}
export default Profile
// src/pages/Setting.jsx (子路由组件)
const Setting = () => {
return <div>账户设置:修改密码、绑定手机号...</div>
}
export default Setting
步骤2:配置嵌套路由规则(核心:children 属性)
在 <Route> 中通过 children 属性,配置当前路由的「子路由规则」,子路由的 path 可以写相对路径(也可以写绝对路径)。
jsx
// src/App.jsx 新增路由配置
import User from './pages/User'
import Profile from './pages/Profile'
import Setting from './pages/Setting'
// 在Routes中新增:
<Route path="/user" element={<User />}>
{/* 子路由规则:children数组包裹子Route */}
<Route path="profile" element={<Profile />} />
<Route path="setting" element={<Setting />} />
</Route>
步骤3:测试效果
- 地址栏访问
http://localhost:5173/user→ 只渲染 User 父组件,<Outlet>位置为空; - 点击「个人资料」→ 地址栏变成
http://localhost:5173/user/profile→<Outlet>位置渲染 Profile 组件; - 点击「账户设置」→ 地址栏变成
http://localhost:5173/user/setting→<Outlet>位置渲染 Setting 组件;
七、路由传参
开发中经常需要在路由跳转时传递参数(比如:列表页跳详情页,传递商品ID/用户ID),React Router v6 提供2种最常用的传参方式,满足所有场景:
方式1:params 动态路由传参(推荐,路径传参,地址栏可见)
1. 核心语法
- 配置路由时:
path="路由名/:参数名"(比如/detail/:id、/user/:userId); - 跳转时:
Link to="/detail/1001"(参数值直接拼在路径后); - 接收参数:使用
useParams()钩子函数,直接解构获取。
2. 完整示例
jsx
// 1. 配置动态路由
<Route path="/detail/:id" element={<Detail />} />
// 2. 跳转传参
<Link to="/detail/1001">查看商品1001详情</Link>
// 3. 接收参数:src/pages/Detail.jsx
import { useParams } from 'react-router-dom'
const Detail = () => {
const { id } = useParams() // 解构获取参数
return <div>商品详情页,商品ID:{id}</div>
}
export default Detail
方式2:search 搜索传参(地址栏拼接,?key=value 格式)
1. 核心语法
- 配置路由时:正常写
path="/search",不需要改路由; - 跳转时:
Link to="/search?keyword=React&page=1"; - 接收参数:使用
useSearchParams()钩子函数(类似 React 的useState)。
2. 完整示例
jsx
// 1. 配置普通路由
<Route path="/search" element={<Search />} />
// 2. 跳转传参
<Link to="/search?keyword=React&page=1">搜索React</Link>
// 3. 接收参数:src/pages/Search.jsx
import { useSearchParams } from 'react-router-dom'
const Search = () => {
const [searchParams] = useSearchParams()
const keyword = searchParams.get('keyword') // 获取单个参数
const page = searchParams.get('page')
return (
<div>
搜索关键词:{keyword} <br/>
当前页码:{page}
</div>
)
}
export default Search
八、编程式导航
上面的 <Link> 是「声明式导航」(写在JSX中的标签跳转),开发中还有大量场景需要在JS逻辑中实现路由跳转(比如:登录成功后跳首页、表单提交后跳列表页),这种方式就是「编程式导航」。
核心API:useNavigate() 钩子函数
1. 基本使用
jsx
import { useNavigate } from 'react-router-dom'
const Login = () => {
// 1. 调用钩子函数,得到导航函数
const navigate = useNavigate()
const handleLogin = () => {
// 模拟登录逻辑
const isLogin = true
if (isLogin) {
// 2. 编程式跳转:跳转到首页
navigate('/')
}
}
return (
<div>
<h2>登录页</h2>
<button onClick={handleLogin}>登录成功跳首页</button>
</div>
)
}
export default Login
2. 编程式传参
jsx
// 方式1:params传参
navigate('/detail/1002')
// 方式2:search传参
navigate('/search?keyword=Vue&page=2')
3. 其他常用功能
jsx
navigate(-1) // 等价于浏览器的「后退」按钮,返回上一页
navigate(1) // 等价于浏览器的「前进」按钮,前进下一页
九、React Router v5 vs v6 核心差异
很多老项目还在用 v5,面试一定会问两者的区别,记住这5个核心差异即可,都是高频考点:
- 路由容器不同:v5 用
<Switch>做路由容器,v6 废弃<Switch>,新增<Routes>替代; - 组件渲染属性不同:v5 的
<Route>用component={Home}渲染组件,v6 用element={<Home />}渲染组件; - 嵌套路由不同:v5 嵌套路由需要写完整路径,v6 用
children属性+<Outlet>实现,更简洁; - 导航API不同:v5 用
useHistory()做编程式导航,v6 废弃useHistory(),新增useNavigate()替代; - 匹配规则不同:v6 的
<Routes>是「精准匹配」,v5 的<Switch>是「模糊匹配」,v6 匹配更精准,性能更高。
总结
- React 本身无内置路由,路由功能完全依赖第三方库
react-router-dom; - 路由核心逻辑:监听地址栏路径变化 → 匹配对应组件 → 渲染到指定位置,实现SPA局部更新;
- v6 核心组件:
<BrowserRouter>(根容器)、<Routes>(规则容器)、<Route>(匹配规则)、<Link>(声明式导航)、<Outlet>(嵌套出口); - 路由传参:
params(动态路由)、search(地址栏拼接),分别用useParams()/useSearchParams()接收; - 编程式导航:
useNavigate()钩子函数,用于JS逻辑中跳转路由; - 面试必考:v6 废弃了
<Switch>、useHistory(),新增了<Routes>、useNavigate()、<Outlet>。