跳到主要内容

4.4-React-Router

Create by fall on 16 Nov 2021 Recently revised in 09 Oct 2023

React-Router

使用路由需要安装下面的包

react-router:提供了router 的核心 API。

react-router-dom:包含 react-router 并且提供了 BrowserRouterRouteLink 等 api,可以通过 dom 操作触发事件控制路由。

  • 路由类型:BrowserRouter(浏览器路由)、HashRouter(哈希路由)

以下为 V6 版本的 React-Router 的使用,和 V5 有很多的不同

路由组件创建路由

  • 根路由:Routers
  • 所有子路由:Router

所有的 Router,都可以继续嵌套 Router,确保初始路径相同

路由配置

// 如果想使用 hash 路由,可以用 HashRouter 包裹
<BrowserRouter>
<Menus />
<Routes>
<Route element={<Home />} path="/home"></Route>
<Route element={<Layout/>} path="/children">
<Route element={<Child1/>} path="/children/child1"></Route>
<Route element={<Child2/>} path="/children/child2"></Route>
</Route>
<Route path='/*' element={<Navigate to="/err" replace />} ></Route>
</Routes>
</BrowserRouter>

在路由为 /children 的页面中,如果想在某些位置插入 <Child1/>,或者 <Child2/>,需要用到 <Outlet/> 路由

import { Outlet } from 'react-router-dom';
const Layout = ()=>(
<div>
这是布局组件,子路由的内容在这里显示:
<Outlet></Outlet>
<div>)

组件中使用 Outlet 就可以把子路由内容插入当前页面中

方法创建路由

通过 createBrowserRouter 创建路由

// App.jsx
import { createBrowserRouter, RouterProvider } from "react-router-dom";
// import route components
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
},
{
path: "/about",
element: <About />,
},
]);
export default function App() {
return <RouterProvider router={router} />;
}
import {Suspense} from 'react'
const CuRu: FC = () => {
const RecOi = React.lazy(() => import('@/pages/contact'))
return (
<Suspense fallback={<span>加载出错</span>}>
<RecOi />
</Suspense>
)
}

const BaseRoute = () => useRoutes([
{
path: '/404',
element: <ErrorPage404 />
},
{
path: '/home',
element: <ContactMe />
},
{
path: '/where',
element: CuRu({})
},
])
export default function () {
return <BrowserRouter>
<BaseRoute></BaseRoute>
</BrowserRouter>
}

createRoutesFromChildren,可以使用 jsx 替代数组

const router = createBrowserRouter(
createRoutesFromChildren(
<>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</>
)
)
export default function App() {
return <RouterProvider router={router} />;
}

通过 useRoutes 创建路由

useRoutes 其实就是 <Routes> 的替代 ,只是风格不同。

function Routes() {
return useRoutes([
{
path: "/",
element: <Home />,
},
{
path: "/about",
element: <About />,
},
]);
}
export default function App() {
return (
<BrowserRouter>
<Routes />
</BrowserRouter>
);
}
type RouteParam = {
caseSensitive?: AgnosticNonIndexRouteObject["caseSensitive"];
path?: AgnosticNonIndexRouteObject["path"];
id?: AgnosticNonIndexRouteObject["id"];
loader?: AgnosticNonIndexRouteObject["loader"];
action?: AgnosticNonIndexRouteObject["action"];
hasErrorBoundary?: AgnosticNonIndexRouteObject["hasErrorBoundary"];
shouldRevalidate?: AgnosticNonIndexRouteObject["shouldRevalidate"];
handle?: AgnosticNonIndexRouteObject["handle"];
index?: false;
children?: RouteObject[];
element?: React.ReactNode | null;
errorElement?: React.ReactNode | null;
Component?: React.ComponentType | null;
ErrorBoundary?: React.ComponentType | null;
lazy?: LazyRouteFunction<NonIndexRouteObject>;
}

路由相关方法

这些方法大多需要在 Router 包裹的组件内部使用

  • useLocation:可以获取 hash | key | pathname | search | state 等状态。
  • useNavigate:实现路由跳转
  • useParams:获取路由上的动态信息(slug)
  • useSearchParams:控制当前链接的查询字符串
import {useParams, useNavigate,useSearchParams} from 'react-router-dom'
// useParams
const {slug} = useParams() // 可以获取(动态路由)后面拼接的内容,在此和下面标记的 slug 相同
const pageRouter = <Route path="posts" element={<Posts />}>
<Route path="/" element={<PostLists />} />
<Route path="/:slug" element={<Post />} />
<!-- 将该路径后的内容,作为 slug 进行保存 -->
</Route>
// useNavigate
const onClick = ()=>{
const navigate = useNavigate()
// 传递跳转路径,以及状态等内容
navigate('/list',{state:'fall'})
}
// useSearchParams
const [searchParams, setSearchParams] = useSearchParams();

const { state } = useLocation()
// history 栈中的一条记录
// interface Location {
// pathname: string; // 继承自Path
// search: string; // 继承自Path
// hash: string; // 继承自Path
// state: unknown; // 当前 location 关联的任意值
// key: string; // 当前 location 关联的唯一字符串
// }

路由嵌套

// Outlet
<Routes>
<Route path="parent" element={<Parent />}>
<Route path="child" element={<Child />} />
</Route>
</Routes>
function Parent() {
return (
<div>
<h1>Parent Component</h1>
<Outlet /> {/* 当访问 "parent/child" 时,Child 组件会在这里渲染 */}
</div>
)
}

路由重定向

// 如果为空,重定向至 /home,如果匹配不到,重定向至 err 页面
<Route path='/' element={<Navigate to="/home" replace />} ></Route>
<Route path='/*' element={<Navigate to="/err" replace />} ></Route>

路由匹配

动态路由

function Team() {
let params = useParams();
console.log(params.teamId); // "hotspur"
}
<Route
// this path will match URLs like
// - /teams/hotspur
path="/teams/:teamId"
element={<Team />}
/>;

可选参数路由

<Route
// this path will match URLs like
// - /categories
// - /en/categories
path="/:lang?/categories"
element={<Categories />}
/>;
function Categories() {
let params = useParams();
console.log(params.lang);
}

通用路由

<Route
// 将会匹配任何以 /files 开头的路由
// - /files
// - /files/one
// - /files/one/two/three
path="/files/*"
element={<Team />}
/>;
// and the element through `useParams`
function Team() {
let params = useParams();
console.log(params["*"]); // "one/two"
}

索引路由

// 访问父路由时渲染,如访问 /teams 时将渲染 TeamsIndex
<Route path="/teams" element={<Teams />}>
<Route index element={<TeamsIndex />} />
<Route path=":id" element={<Team />} />
</Route>

实现原理

Route

Route 本质上只提供将数据格式化的过程。Routes 就是将数据化的对象,通过 useRoutes 进行渲染为 jsx。所有的内容都会在 Routes 中实现。

// Route 
function Route(_props){
invariant(
false,
`A <Route> is only ever to be used as the child of <Routes> element, ` +
`never rendered directly. Please wrap your <Route> in a <Routes>.`
);
}

Routes

Routes 本质上是通过 useRoutes 返回的 react element 对象,那么可以理解成此时的 useRoutes 作为一个视图层面意义上的 hooks 。 Routes 本质上就是使用 useRoutes 。

export function Routes({
children,
location,
}: RoutesProps): React.ReactElement | null {
return useRoutes(createRoutesFromChildren(children), location);
}

createRoutesFromChildren,就和函数名称意义相同,将 children 作为参数,生成 Router 导航对象

所以,本质上都是使用 useRoutes 进行生成的路由,因此,路由内容的更改都是内部进行实现,所以可以使用 Outlet 进行嵌套

参考文章

作者文章名称
MoonLightReact 快速暴力入门
我不是外星人「React进阶」react-router v6 通关指南
前端迷悟2023-React Router v6简明教程-使用篇