菜鸟教程 -- 学的不仅是技术,更是梦想!

React 教程
(追記) (追記ここまで)

路由跳转

本章你将学会用 React Router v6 实现无刷新的页面跳转,包括动态路由和参数读取。


为什么要用路由?

传统的多页面网站,每次点击链接都要向服务器请求新页面,有白屏闪烁。

前端路由 让 URL 变化时由 JS 控制显示哪个组件,不向服务器发请求。

好处:切换流畅,体验像原生 APP


React Router v6 核心概念

概念作用对应代码
路由表定义 URL 与组件的映射<Routes> + <Route>
路由出口子路由显示的占位位置<Outlet />
导航链接点击跳转<Link to="...">
动态路由URL 中的变量/post/:id
读取参数获取动态路由中的值useParams()

安装与配置

$ npm install react-router-dom@6

创建路由配置

实例

// 文件路径:src/router/index.jsx
import { createBrowserRouter } from 'react-router-dom'
import App from '../App'
import HomePage from '../pages/HomePage'
import PostPage from '../pages/PostPage'

// createBrowserRouter 创建路由实例
const router = createBrowserRouter([
{
path: '/', // App 作为根布局
element: <App />,
children: [
{
index: true, // index: true 表示 / 路径的默认子路由
element: <HomePage />
},
{
path: 'post/:id', // 动态路由::id 匹配任意值
element: <PostPage />
}
]
}
])

export default router

两种路由模式:createBrowserRouter 产生干净的 URL(/post/1),需要服务器配置。如果部署环境不确定,用 createHashRouter,URL 中带 # 号(/#/post/1),无需服务器配置。

在 main.jsx 中注册路由

实例

// 文件路径:src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import router from './router'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
{/* RouterProvider 将路由注入到应用 */}
<RouterProvider router={router} />
</React.StrictMode>
)

在 App.jsx 中使用 Outlet

实例

// 文件路径:src/App.jsx
import { Outlet } from 'react-router-dom'
import NavBar from './components/NavBar'
import './App.css'

function App() {
return (
<div className="app">
<NavBar />
<main className="container">
{/* Outlet 是嵌套路由的出口:子路由匹配到的组件会在这里渲染 */}
<Outlet />
</main>
<footer className="footer">
<p>© 2024 RUNOOB Blog. Powered by React.</p>
</footer>
</div>
)
}

export default App

Link — 声明式导航

Link 替代了传统的 <a> 标签,不会触发页面刷新。

实例

import { Link } from 'react-router-dom'

function BlogCard({ id, title, summary, date, category }) {
return (
<Link to={`/post/${id}`} className="card-link">
<div className="card">
<span className="tag">{category}</span>
<h3>{title}</h3>
<p>{summary}</p>
<span className="date">{date}</span>
</div>
</Link>
)
}

不要用 <a href="...">——它会导致浏览器向服务器请求新页面,整个 React 应用会重新初始化。用 <Link to="..."> 才能在客户端无刷新切换。


useParams — 读取路由参数

在详情页中,需要从 URL 中取出文章 ID,才能找到对应文章。

实例

import { useParams } from 'react-router-dom'

function PostPage() {
// useParams 返回一个对象,包含 URL 中的参数
const { id } = useParams()
// 注意:id 是字符串,需要数字时可以 Number(id) 转换

return (
<div>
<p>当前文章 ID:{id}</p>
</div>
)
}

useNavigate — 编程式跳转

某些场景下需要不通过点击链接来跳转(如:表单提交后跳转、未找到文章时跳转回首页)。

实例

import { useNavigate } from 'react-router-dom'

function PostPage() {
const navigate = useNavigate()

const article = null // 假设没找到文章

if (!article) {
// 文章不存在时,跳转回首页
return (
<div>
<p>文章不存在</p>
<button onClick={() => navigate('/')}>返回首页</button>
</div>
)
}

return <div>文章详情...</div>
}

Vue3 vs React 路由 API 对照

功能Vue3React
导航链接<RouterLink><Link>
路由出口<RouterView><Outlet>
动态路由/post/:id/post/:id
读取参数useRoute().paramsuseParams()
编程式跳转useRouter().push()useNavigate()

动手:完整的详情页 + 路由整合

第一步:改造 BlogCard 支持点击跳转

实例

// 文件路径:src/components/BlogCard.jsx
import { Link } from 'react-router-dom'

function BlogCard({ id, title, summary, date, category }) {
return (
<Link to={`/post/${id}`} className="card-link">
<div className="card">
<span className="tag">{category}</span>
<h3>{title}</h3>
<p>{summary}</p>
<span className="date">{date}</span>
</div>
</Link>
)
}

export default BlogCard

第二步:创建 HomePage 和 PostPage

实例

// 文件路径:src/pages/HomePage.jsx
import { useState, useMemo } from 'react'
import BlogCard from '../components/BlogCard'
import CategoryFilter from '../components/CategoryFilter'

function HomePage() {
const [articles] = useState([
{ id: 1, title: 'React 入门完全指南', summary: '从零学 React', category: 'React', date: '2024-05-10',
content: '<h2>为什么学 React?</h2><p>React 是目前最流行的前端框架之一...</p>' },
{ id: 2, title: 'JS 异步编程详解', summary: '搞懂 Promise 和 async/await', category: 'JavaScript', date: '2024-05-08',
content: '<h2>什么是异步?</h2><p>JS 是单线程的,异步操作可以让主线程不阻塞...</p>' },
{ id: 3, title: 'CSS Grid 布局实战', summary: '用 Grid 实现响应式布局', category: 'CSS', date: '2024-05-05',
content: '<h2>Grid 入门</h2><p>Grid 是二维布局系统...</p>' },
])

const [activeCategory, setActiveCategory] = useState('全部')

const categories = useMemo(() => {
return ['全部', ...new Set(articles.map(a => a.category))]
}, [articles])

const filteredArticles = useMemo(() => {
if (activeCategory === '全部') return articles
return articles.filter(a => a.category === activeCategory)
}, [articles, activeCategory])

return (
<div>
<h2 className="section-title">最新文章</h2>
<CategoryFilter
categories={categories}
activeCategory={activeCategory}
onCategoryChange={setActiveCategory}
/>
<p className="result-info">{filteredArticles.length}</p>
{filteredArticles.length === 0 ? (
<p className="empty-tip">该分类下暂无文章</p>
) : (
<div className="article-grid">
{filteredArticles.map(article => (
<BlogCard key={article.id} {...article} />
))}
</div>
)}
</div>
)
}

export default HomePage

实例

// 文件路径:src/pages/PostPage.jsx
import { useParams, Link } from 'react-router-dom'
import { useMemo } from 'react'

// 文章数据(后续章节会从外部加载)
const articles = [
{ id: 1, title: 'React 入门完全指南', category: 'React', date: '2024-05-10',
content: '<h2>为什么学 React?</h2><p>React 是目前最流行的前端框架之一...</p>' },
{ id: 2, title: 'JS 异步编程详解', category: 'JavaScript', date: '2024-05-08',
content: '<h2>什么是异步?</h2><p>JS 是单线程的...</p>' },
]

function PostPage() {
const { id } = useParams()

// 根据 ID 查找文章
const article = useMemo(() => {
return articles.find(a => a.id === Number(id))
}, [id])

// 文章不存在
if (!article) {
return (
<div className="not-found">
<h2>文章不存在</h2>
<p>找不到 ID 为 {id} 的文章</p>
<Link to="/">返回首页</Link>
</div>
)
}

// 文章存在
return (
<article className="post-view">
<span className="category-tag">{article.category}</span>
<h1>{article.title}</h1>
<time>{article.date}</time>
{/* dangerouslySetInnerHTML 等价于 Vue 的 v-html,注意 XSS 风险 */}
<div
className="content"
dangerouslySetInnerHTML={{ __html: article.content }}
/>
<Link to="/" className="back-link">← 返回首页</Link>
</article>
)
}

export default PostPage

dangerouslySetInnerHTML 是 React 版的 v-html。名字特意取得很长,提醒你:不要用它渲染用户输入的内容,有 XSS 安全风险。这里的内容是开发者自己写的,所以是安全的。

添加 PostPage 样式

实例

/* 文件路径:src/App.css 追加详情页样式 */
.post-view {
max-width: 720px;
margin: 0 auto;
}

.category-tag {
display: inline-block;
padding: 4px 12px;
background: #e3f2fd;
color: #1976d2;
border-radius: 12px;
font-size: 13px;
margin-bottom: 12px;
}

.post-view h1 {
font-size: 32px;
margin-bottom: 12px;
line-height: 1.4;
}

.post-view time {
display: block;
color: #999;
font-size: 14px;
margin-bottom: 30px;
}

.content {
line-height: 1.8;
font-size: 16px;
color: #333;
}

.content h2 {
margin: 24px 0 12px;
font-size: 22px;
}

.content p {
margin-bottom: 12px;
}

.back-link {
display: inline-block;
margin-top: 40px;
color: #1976d2;
text-decoration: none;
}

.not-found {
text-align: center;
padding: 60px 0;
}

本章小结

本章你学会了 React Router v6 的核心:路由表配置(createBrowserRouter)、Link 导航、Outlet 嵌套出口、动态路由 /post/:id、useParams 读取参数、useNavigate 编程式跳转。

现在博客有了真正的多页面体验——首页浏览列表,点击进入详情页阅读完整内容。

AI 思考中...

点我分享笔记

  • 昵称 (必填)
  • 邮箱 (必填)
  • 引用地址

AltStyle によって変換されたページ (->オリジナル) /