路由
你可能已经意识到了一个问题,我们的应用现在只有一个页面,这显然是不够的。在这一节中,我们将学习如何使用 React Router 来实现页面路由。
什么是路由
路由 是指根据不同的 URL 地址,展示不同的内容。比如说:
https://example.com/ -> 首页https://example.com/about -> 关于页面https://example.com/contact -> 联系页面这些不同的 URL 地址对应着不同的页面,这就是 路由 的概念。
React Router
在 React 中,我们可以使用 React Router 来快速实现路由,通过 VSCode 打开终端,输入以下命令来安装 React Router:
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>);让我们来逐行理解这段代码:
- BrowserRouter和- Routes是- React 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 来表示页面上展示的提示条序号,比如说访问 id 为 1 的页面,我们展示 提示条 #1,访问 id 为 2 的页面,我们展示 提示条 #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 之外的代码了,Outlet 是 React Router 提供的一个组件,用于展示子路由的内容。稍后你就会体会到它的作用。
然后,我们要创建三个页面用于展示我们的模板框架,我们可以在 pages 文件夹下新建一个文件夹 temp,然后在这个文件夹中新建三个文件 Page1.tsx、Page2.tsx 和 Page3.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 中找到对应的代码。