Router 组件
首先第一步我们需要将我们的<App/>组件包裹在<Router>组件中(React Router)。有多种可用路由,但常用的有以下两种:
它们主要区别在它们创建的url中很明显:
// <BrowserRouter>
http://localhost:3000/home
// <HashRouter>
http://localhost:3000/#/home
之所以<BrowserRouter>常用,因为它利用HTML5 History Api将UI与URL同步,提供干净的URL结构,无需哈希片段。而<HashRouter>利用URL(window.location.hash)的哈希部分来管理路由,在无法进行服务器配置的环境中或缺乏HTML5 History API支持的旧版浏览器非常有用。可以在此处了解有关差异的更多信息。
另请注意, React Router 的最新版本中引入了四个新路由器,它们支持各种新数据 API 。在此文档我们将重点关注传统路由器,因为它们功能强大、文档齐全并在互联网上的无数项目中使用。
因此,让我们导入该<BrowserRouter>
组件并将其包装在该<App>
组件周围。更改index.js
为如下所示:
// src/index.tsx 或 src/index.js
import React from 'react'
import ReactDOM from 'react-dom' // import ReactDOM from 'react-dom/client';
import App from './App'
import { BrowserRouter } from 'react-router-dom'
// index.tsx
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root'))
// src/index.js
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
此代码history
为我们的整个<App>
组件创建一个实例。
history
库可让JavaScript 运行的任何地方轻松管理会话历史记录。对象history
抽象了各种环境中的差异,并提供了一个最小的 API,可以管理历史堆栈、导航并在会话之间保留状态。
每个<Router>
组件都会创建一个history
对象来跟踪堆栈中的当前位置和先前位置。当当前位置发生变化时,视图将重新渲染,导航也会更新。
当前位置如何变化?在React Router v6中,useNavigate钩子提供了一个navigate
可以用于此目的的函数。navigate
当您单击组件时会调用该函数<Link>
,并且还可以通过传递带有replace: true
属性的选项对象来替换当前位置。
其他方法(例如navigate(-1)
后退和navigate(1)
前进)用于通过后退或前进页面来浏览历史堆栈。
应用程序不需要创建自己的历史对象;该任务由组件管理<Router>
。简而言之,它创建一个历史对象,订阅堆栈中的更改,并在 URL 更改时修改其状态。这会触发应用程序的重新渲染,确保显示正确的 UI。
Link 和 Route 组件
<Route>组件是React Router 中最重要的组件。如果位置与当前路线路径匹配,他会呈现对应UI。正常情况下,<Router>组件应该有path属性,如果路径名与当前位置匹配,则渲染对应UI。
<Link>组件用于页面间的导航。它与HTML锚元素相当。但是还使用锚链接会导致整个页面刷新,这不是我们期望的状态。因此,我们可以使用<Link>导航到特定的URL并重新渲染视图无需刷新。
更新src/App.tsx为如下内容:
import { Link, Route, Routes } from 'react-router-dom';
const Home = () => (
<div>
<h2>Home</h2>
<p>Welcome to our homepage!</p>
</div>
);
const Categories = () => (
<div>
<h2>Categories</h2>
<p>Browse items by category.</p>
</div>
);
const Products = () => (
<div>
<h2>Products</h2>
<p>Browse individual products.</p>
</div>
);
export default function App() {
return (
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/categories">Categories</Link>
</li>
<li>
<Link to="/products">Products</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/categories" element={<Categories />} />
<Route path="/products" element={<Products />} />
</Routes>
</div>
);
}
在App.tsx页面中我们声明了三个组件:<Home>、<Categories>和<Products>它们代表应用程序中的不同页面。<Routes>从React Router 导入组件<Route>用于定义路由逻辑。
在<App>组件中,我们有一个基本的导航菜单,其中每个项目都是<Link>React Router 的一个组件。这些<Link>组件用于创建到应用程序不同部分的导航链接,每个部分都有特定路径(分别为/
、/categories
和/products
)相关联。请注意,在较大的应用程序中,可以将此菜单封装在布局组件中,以在不同视图之间保持一致的结构。您可能还想向当前选定的导航项添加某种活动类(例如使用NavLink 组件)。然而,为了保持重点,我们将在这里跳过这一点。
在导航菜单下方,该<Routes>
组件用作单个<Route>
组件集合的容器。每个<Route>
组件都有一个特定的路径和一个与当前 URL 匹配时渲染的 React 组件相关联。例如,当 URL 为 时/categories
,将渲染组件<Categories>
。
注意:在 React Router 的早期版本中,/
会匹配/
和/categories
,这意味着两个组件都被渲染。解决方案是将exact
prop 传递给<Route>
,确保仅匹配确切的路径。此行为在 v6 中发生了变化,因此现在默认情况下所有路径都完全匹配。正如我们将在下一节中看到的,如果您因为有子路由而想要匹配更多 URL,请使用尾随*
— 例如<Route path="categories/*" ...>。
嵌套路由
顶级路由虽然很好,但是大多数的应用需要能够嵌套路由。例如,显示特定产品或编辑特定用户。
<Route>在React Router v6中,通过将组件放置<Route>在jsx中的其他自建内来嵌套路由。这样,嵌套<Route>组件自然地反映了它们所表示的URL的嵌套结构。
如何在程序中实现。修改App.tsx(其中...代表之前的内容保持不变):
import { Link, Route, Routes } from 'react-router-dom';
import { Categories, Desktops, Laptops } from './Categories';
const Home = () => ( ... );
const Products = () => ( ... );
export default function App() {
return (
<div>
<nav>...</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/categories/" element={<Categories />}>
<Route path="desktops" element={<Desktops />} />
<Route path="laptops" element={<Laptops />} />
</Route>
<Route path="/products" element={<Products />} />
</Routes>
</div>
);
}
我们已将该<Categories>
组件移至其自己的页面中,并且现在正在导入另外两个组件,即<Desktops>
和<Laptops>
。
我们还对该<Routes>
组件进行了一些更改。
首先,在App.js 同级目录下创建了Categories.tsx
然后添加以下代码:
// src/Categories.tsx
import { Link, Outlet } from 'react-router-dom';
export const Categories = () => (
<div>
<h2>Categories</h2>
<p>Browse items by category.</p>
<nav>
<ul>
<li>
<Link to="desktops">Desktops</Link>
</li>
<li>
<Link to="laptops">Laptops</Link>
</li>
</ul>
</nav>
<Outlet />
</div>
);
export const Desktops = () => <h3>Desktop PC Page</h3>;
export const Laptops = () => <h3>Laptops Page</h3>;
页面刷新后,点击“类别”链接。可以看到两个新的菜单(台式机和笔记本),点击任意菜单将在原始类别页面显示一个新的页面。
App.tsx文件中我们将path改为/categories如下所示:
<Route path="/categories/" element={<Categories />}>
<Route path="desktops" element={<Desktops />} />
<Route path="laptops" element={<Laptops />} />
</Route>
在更新的代码中,<Route>
组件/categories
已被修改为<Route>
在其中包含两个嵌套组件 - 一个 for /categories/desktops
,另一个 for/categories/laptops
。此更改说明了 React Router 如何实现其路由配置的可组合性。
<Route>
通过在 <Route>
中嵌套组件/categories
,使我们能够创建更加结构化的 URL 和 UI 层次结构。这样,当用户导航到/categories/desktops
或 /categories/laptops
时,相应的<Desktops>
或<Laptops>
组件将在组件<Categories>
内呈现,从而展示路由和组件之间清晰的父子关系。
注意:嵌套路由的路径是通过将其祖先的路径与其自己的路径连接起来自动组成的。
我们还更改了<Categories>
组件让其包含<Outlet />
:
export const Categories = () => (
<div>
<h2>Categories</h2>
...
<Outlet />
</div>
);
<Outlet>
被放置在父路由元素中以渲染其子路由元素。这允许在渲染子路由时显示嵌套的 UI。
这种组合方法使路由配置更具声明性且更易于理解,与 React 基于组件的架构很好地保持一致。