跳转到内容

路由

你可能已经意识到了一个问题,我们的应用现在只有一个页面,这显然是不够的。在这一节中,我们将学习如何使用 React Router 来实现页面路由。

什么是路由

路由 是指根据不同的 URL 地址,展示不同的内容。比如说:

https://example.com/ -> 首页
https://example.com/about -> 关于页面
https://example.com/contact -> 联系页面

这些不同的 URL 地址对应着不同的页面,这就是 路由 的概念。

React Router

React 中,我们可以使用 React Router 来快速实现路由,通过 VSCode 打开终端,输入以下命令来安装 React Router

Terminal window
npm install react-router-dom

创建一个页面

首先,在 src 目录下新建一个 pages 文件夹,然后在这个文件夹中新建一个 HelloWorld.tsx 文件,用于存放我们之前的 main 组件。尝试着自己完成这个步骤,如果遇到问题,可以查看下面的代码示例。

import Notice from "../components/Notice"
const HelloWorld = () => {
return (
<>
<Notice title="这是一个标题" content="这是一个内容" />
<p>
Hello world!
</p>
</>
)
}
export default HelloWorld

现在,我们已经有了一个页面,接下来,让我们开始配置路由。

配置 main.tsx

在我们开始之前,让我们先备份一下当前的 main.tsx 文件,让我们重命名为 main-helloWorld.tsx。然后,新建一个 main.tsx 文件,我们可以在这个文件中写入以下代码:

import { createRoot } from "react-dom/client";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import HelloWorld from "./pages/HelloWorld";
createRoot(document.getElementById('root')!).render(
<BrowserRouter>
<Routes>
<Route index element={<HelloWorld />} />
<Route path="/hello" element={<HelloWorld />} />
<Route path="/hello/*" element={<HelloWorld />} />
<Route path="/hello/:id" element={<HelloWorld />} />
</Routes>
</BrowserRouter>
);

让我们来逐行理解这段代码:

  • BrowserRouterRoutesReact Router 提供的一个组件,用于包裹整个应用,在这里我们直接使用就好。
  • Route 则用于配置路由,index 表示默认路由,path 表示路径,element 表示对应的组件。
  • 这里我们可以看到几种特殊的用法:
    • index 表示默认路由
    • path="/hello" 表示路径为 /hello 的路由
    • path="/hello/*" 表示 /hello 开头的所有路径的路由。这里的 * 是通配符,表示所有路径。
    • path="/hello/:id" 表示 /hello 开头的所有路径的路由,其中 :id 是一个参数,可以在组件中通过 useParams 来获取。

现在,你可以打开浏览器,尝试以下链接,看看效果:

  • http://localhost:5173/
  • http://localhost:5173/hello
  • http://localhost:5173/hello/123
  • http://localhost:5173/hello/abc/a

这些路径都会展示我们的 HelloWorld 页面。

路径参数

在刚刚的内容中,我们给出了 path="/hello/:id" 这样的路由,这种路由可以接受一个参数,这个参数可以在组件中通过 useParams 来获取。现在,比方说我们希望用 id 来表示页面上展示的提示条序号,比如说访问 id1 的页面,我们展示 提示条 #1,访问 id2 的页面,我们展示 提示条 #2,以此类推。让我们来实现这个功能。

首先,进入 HelloWorld.tsx 文件,就和其他的函数一样,在页面中,在返回之前,我们也可以进行其他操作。现在,让我们在 return 前,添加 const { id } = useParams();,这样我们就可以通过 id 来获取到路径参数了。注意,useParams 是一个 React Router 提供的 hook(钩子,一种特别的函数),我们需要先引入它,所以在文件的开头,我们需要添加 import { useParams } from "react-router-dom";

之后,我们就可以使用 { id } 来获取到路径参数 id 了,接下来,我们可以根据 id 来展示不同的提示条。让我们来看看完整的代码:

import { useParams } from "react-router-dom";
import Notice from "../components/Notice"
const HelloWorld = () => {
const { id } = useParams();
return (
<>
<Notice title={ "提示条 #" + id } content="这是一个内容" />
<p>
Hello world!
</p>
</>
)
}
export default HelloWorld

现在,你可以尝试访问 http://localhost:5173/hello/2,你应该可以看到:

提示条 #2

这是一个内容

模板框架

让我们观察一下常见的网站,比如说你现在看到的这个教程页面。其实你会发现,无论你在看教程的哪个部分,大体的布局是不变的,只有内容是变化的。这就是一个模板框架的概念,在这个部分,我们会尝试着实现一个简单的模板框架。就像这样:

测试页面

页面 1

测试页面

页面 2

测试页面

页面 3

我们假设我们有三个页面,我们希望能够实现这三个页面都能有一个阴影效果作为页面的背景,并给每个页面都添加一个 “测试页面” 的标题。让我们来实现这个功能!

通过路由实现(推荐)

首先,让我们在 pages 文件夹下新建一个 Template.tsx 文件,用于存放我们的模板框架。在这个文件中,我们可以写入以下代码:

import { Outlet } from "react-router-dom"
const Template = () => {
return (
<div
style={{
width: '100%',
padding: '10px',
backgroundColor: '#f9f9f9',
boxShadow: '0 2px 5px rgba(0, 0, 0, 0.1)',
}}
>
<h3>测试页面</h3>
<Outlet />
</div>
)
}
export default Template

相信你已经可以看懂除了 Outlet 之外的代码了,OutletReact Router 提供的一个组件,用于展示子路由的内容。稍后你就会体会到它的作用。

然后,我们要创建三个页面用于展示我们的模板框架,我们可以在 pages 文件夹下新建一个文件夹 temp,然后在这个文件夹中新建三个文件 Page1.tsxPage2.tsxPage3.tsx。在这三个文件中,我们直接使用之前介绍的 rafce 快捷键快速生成页面即可。

接下来,打开 main.tsx 文件,我们希望创建一个新的 temp/ 路由,并且在这个路由下展示我们的三个页面。让我们在 <Route path="/hello/:id" element={<HelloWorld />} /> 下方添加以下代码:

<Route path="/temp/" element={<Template />} >
<Route index element={<Page1 />} />
<Route path="1" element={<Page1 />} />
<Route path="2" element={<Page2 />} />
<Route path="3" element={<Page3 />} />
</Route>

在这里,我们用一个 Route 包裹了另外三个 Route,这样我们就可以在 /temp/ 下展示我们的三个页面了。其中,我们重复利用 Page1 页面,让它即可以作为默认页面,也可以作为 1 页面。

现在,当你访问 http://localhost:5173/temp/ 或者 http://localhost:5173/temp/1 时,你应该可以看到:

测试页面

Page1

当你访问 http://localhost:5173/temp/2 时,你应该可以看到:

测试页面

Page2

当你访问 http://localhost:5173/temp/3 时,你应该可以看到:

测试页面

Page3

这样,我们就实现了一个简单的模板框架。

通过组件实现

除此之外,你还可以通过组件的方式来实现模板框架。相比之下,这种方法在每个页面都需要单独调用,没有路由方便。但是,最好还是了解一下这种方法。现在,在 components 文件夹下新建一个 TemplateComponent.tsx 文件,用于存放我们的模板框架。在这个文件中,我们可以写入以下代码:

const TemplateComponent = ( props: { children: JSX.Element } ) => {
return (
<div
style={{
width: '100%',
padding: '10px',
backgroundColor: '#f9f9f9',
boxShadow: '0 2px 5px rgba(0, 0, 0, 0.1)',
}}
>
<h3>测试页面</h3>
{props.children}
</div>
);
}
export default TemplateComponent

观察这个文件。由于只有一个 children 参数,我们直接用 : 来定义参数类型,而不是之前的 interface。这是一种简化的写法,你可以根据自己的喜好来选择,如果你还是希望使用 interface,你可以这样写:

interface Props {
children: JSX.Element
}
const TemplateComponent = ( props: Props ) => {...}

可以看到,JSX 元素是一种特殊的类型,它表示一个 React 元素。并且 children 是一个特殊的属性,它表示组件的子元素。这是什么意思呢?通常来说,你可能认为给 children 传递参数应该使用:

<TemplateComponent children={<p>测试页面</p>} />

这样固然没错,但是 React 提供了一种更简便的写法,你可以直接在组件标签中写入子元素,就像这样:

<TemplateComponent>
<p>测试页面</p>
</TemplateComponent>

这就是 children 独有的写法。然后,我们就可以用 {props.children} 来获取到子元素了。 现在让我们回到 HelloWorld.tsx 文件,我们可以添加 TemplateComponen 组件,来实现模板框架。现在,我希望你能把之前写的内容包裹进 TemplateComponent 组件中,尝试自己完成吧!这是你需要实现的效果:

测试页面

提示条 #{id}

这是一个内容

Hello world!

这是我的写法:

import { useParams } from "react-router-dom";
import Notice from "../components/Notice"
import TemplateComponent from "../components/TemplateComponent";
const HelloWorld = () => {
const { id } = useParams();
return (
<>
<TemplateComponent>
<>
<Notice title={"提示条 #" + id} content="这是一个内容" />
<p>
Hello world!
</p>
</>
</TemplateComponent>
</>
)
}
export default HelloWorld
点击查看答案

跳转页面

在传统的 HTML 网站中,我们会使用 <a href="..."> 标签来实现页面跳转。但是在 React 中,我们不推荐这样做。因为这样会导致页面刷新和页面的状态丢失,而且会影响用户体验。在 React 中,我们推荐使用 useNavigate 函数来实现页面跳转。比如说,我们希望在点击一个按钮时跳转到 /temp/1 页面,我们可以这样写:

import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
<button onClick={() => navigate('/temp/1')}>跳转到 /hello/2</button>

在这里,我们首先引入了 useNavigate 函数,然后把他赋值给 navigate 变量。之后,我们在按钮的 onClick 事件中(当用户点击时)调用 navigate 函数,并传入我们希望跳转的路径。这样,我们就可以实现页面的跳转了。

现在,轮到你来实现了!在 HelloWorld.tsx 文件中,添加一个按钮,当用户点击时,跳转到 /temp/1 页面。这是我的答案:

import { useNavigate, useParams } from "react-router-dom";
import Notice from "../components/Notice"
import TemplateComponent from "../components/TemplateComponent";
const HelloWorld = () => {
const { id } = useParams();
const navigate = useNavigate();
return (
<>
<button onClick={() => navigate('/temp/1')}>跳转到 /hello/2</button>
<TemplateComponent>
<>
<Notice title={"提示条 #" + id} content="这是一个内容" />
<p>
Hello world!
</p>
</>
</TemplateComponent>
</>
)
}
export default HelloWorld
点击查看答案

现在,你可以尝试点击按钮,看看效果!

总结

路由的内容比较多,并且路由这个概念对你来说可能也比较陌生。如果你对部分内容不确定,可以多看几遍,始终记得你可以在 前端课程 GitHub 中找到对应的代码。