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
并且提供了 BrowserRouter
、Route
、Link
等 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 进行嵌套
参考文章
作者 | 文章名称 |
---|---|
MoonLight | React 快速暴力入门 |
我不是外星人 | 「React进阶」react-router v6 通关指南 |
前端迷悟 | 2023-React Router v6简明教程-使用篇 |