react-router
数据模式, 声明模式
- 推荐使用数据模式
<Link />,<NavLink />类似 Vue 的<RouterLink />
数据模式
@/router/index.tsx
@/router/index.tsx
import Home from "@/pages/Home";
import About from "@/pages/About";
import { createBrowserRouter } from "react-router";
export const router = createBrowserRouter([
{
path: "/",
Component: Home, // Component
},
{
path: "/about",
element: <About />, // element
},
]);声明模式
import { BrowserRouter, Route, Routes } from "react-router";
import Home from "@/pages/Home";
import About from "@/pages/About";
import { createRoot } from "react-dom/client";
const container = document.getElementById("root")!;
const root = createRoot(container);
root.render(
<BrowserRouter>
<Routes>
{/* Component */}
<Route path="/" Component={Home} />
{/* element */}
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>,
);路由模式
createBrowserRouter: 使用 html5 的 history API (pushState, replaceState, popState), url 中没有 #, 需要服务器配置 fallback 路由, 以解决用户直接访问或刷新非 / 根路径的页面时, 返回 404 Not Found 问题createHashRouter: 使用 url 的 hash 值, 改变 url 中的 hash 值不会导致页面的重新加载, 通常用于单页面内的导航, 不需要服务器配置, 不利于 SEOcreateMemoryRouter: 适用于 node 环境和 SSR, url 不会改变createStaticRouter: 适用于 SSR
nginx 配置 fallback 路由
解决用户直接访问或刷新非 / 根路径的页面时, 返回 404 Not Found 问题
# 检查配置文件是否有语法错误
nginx -t
# 重新加载配置文件
nginx -s reload c// nginx.conf
http {
server {
listen 80;
server_name localhost;
location / {
root html
index index.html
// try_files $uri $uri.html $uri/ =404; // [!code --]
try_files $uri $uri.html $uri/ /index.html; // [!code ++]
}
}
}useNavigate 编程式导航
@/router/index.tsx
@/router/index.tsx
import App from "@/App";
import { lazy } from "react";
import { createBrowserRouter } from "react-router";
export const router = createBrowserRouter([
{
path: "/",
Component: App,
},
{
path: "/home",
Component: lazy(() => import("@/pages/Home")),
},
{
path: "/about",
Component: lazy(() => import("@/pages/About")),
},
]);<Outlet /> 组件
父组件使用 <Outlet /> 组件, 作为子路由组件的容器, 类似 Vue 的 <RouterView />
嵌套路由, 索引路由, 布局路由, 前缀路由
- 嵌套路由: 有 children 属性, 需要使用
<Outlet />组件 - 索引路由:
index: true, 即默认二级路由 - 布局路由: 没有 path 属性, 只提供统一的页面布局
- 前缀路由: 没有 Component 或 element 属性, 只提供统一的路由前缀
嵌套路由, 索引路由
嵌套路由, 索引路由
import Home from "@/pages/Home";
import Layout from "@/pages/Layout";
import { lazy } from "react";
import { createBrowserRouter } from "react-router";
export const router = createBrowserRouter([
{
path: "/layout",
element: <Layout />,
// 嵌套路由: 有 children 属性
children: [
{
// 索引路由: index: true, 即默认二级路由
index: true,
Component: Home,
},
{
path: "home", // 等价于 path: "/layout/home"
Component: lazy(() => import("@/pages/Home")),
},
{
path: "/layout/about", // 等价于 path: "about"
Component: lazy(() => import("@/pages/About")),
},
],
},
]);fallback 路由
import { createBrowserRouter } from "react-router";
export const router = createBrowserRouter([
// ...
{
path: "*",
element: <>Not Found</>,
},
]);路由传参
hook: useSearchParams (url 查询参数)
@/router/index.tsx
@/router/index.tsx
import { lazy } from "react";
import { createBrowserRouter } from "react-router";
export const router = createBrowserRouter([
{
Component: lazy(() => import("@/pages/Layout")),
children: [
{
path: "/home",
Component: lazy(() => import("@/pages/Home")),
},
{
path: "/about",
Component: lazy(() => import("@/pages/About")),
},
],
},
]);hook: useParams (url 路径参数)
@/router/index.tsx
@/router/index.tsx
import { lazy } from "react";
import { createBrowserRouter } from "react-router";
export const router = createBrowserRouter([
{
Component: lazy(() => import("@/pages/Layout")),
children: [
{
path: "/home",
Component: lazy(() => import("@/pages/Home")),
},
{
path: "/about/:id/:project",
Component: lazy(() => import("@/pages/About")),
},
],
},
]);使用 state 传递参数
使用 state 传递的参数, url 中不显示, 不方便通过 url 分享
@/router/index.tsx
@/router/index.tsx
import { lazy } from "react";
import { createBrowserRouter } from "react-router";
export const router = createBrowserRouter([
{
Component: lazy(() => import("@/pages/Layout")),
children: [
{
path: "/home",
Component: lazy(() => import("@/pages/Home")),
},
{
path: "/about",
Component: lazy(() => import("@/pages/About")),
},
],
},
]);hooks: useNavigate, useLocation, useNavigation
懒加载: 延迟加载路由组件, 代码分包
- useNavigate: 获取路由器对象
- useLocation: 获取路由对象
- useNavigation: 获取导航状态
navigation.state: idle 空闲, loading 加载, submitting 提交- 路由导航时, 导航状态 idle -> loading -> idle
@/router/index.tsx
@/router/index.tsx
import App from "@/App";
import { lazy, Suspense } from "react";
import { createBrowserRouter } from "react-router";
const Home = lazy(() =>
new Promise((resolve) => {
setTimeout(resolve, 5000);
}).then(() => import("@/pages/Home")),
);
export const router = createBrowserRouter([
{
path: "/",
Component: App,
children: [
{
path: "/home",
// react 提供的懒加载: 延迟加载路由组件, 代码分包
// 需要配合 <Suspense /> 异步组件使用
// 路由导航时, 导航状态始终是 idle
element: (
<Suspense fallback="请等待 Home 加载...">
<Home />
</Suspense>
),
},
{
path: "/about",
// react-router 提供的懒加载: 延迟加载路由组件, 代码分包
// 路由导航时, 导航状态 idle -> loading -> idle
lazy: async () => {
await new Promise((resolve) => {
setTimeout(resolve, 5000);
});
const About = await import("@/pages/About");
return {
Component: About.default,
};
},
},
],
},
]);路由操作: loader, action
- loader 用于查询, GET 请求会触发 loader
- loader 路由导航时, 导航状态 idle -> loading -> idle
- action 用于增删改, POST, DELETE, PATCH 请求会触发 action
- action 路由导航时, 导航状态 idle -> submitting -> loading -> idle
ErrorBoundary
loader 或 action 抛出错误时, fallback 到 ErrorBoundary
vite.config.ts
vite.config.ts
import { defineConfig, type Plugin } from "vite";
import react from "@vitejs/plugin-react";
import { fileURLToPath, URL } from "node:url";
const vitePluginServer = (): Plugin => {
return {
name: "vite-plugin-server",
configureServer(server) {
const data = [
{ name: "foo", age: 22 },
{ name: "bar", age: 23 },
];
server.middlewares.use("/queryUsers", async (req, res) => {
await new Promise((resolve) => {
setTimeout(resolve, 5000);
});
res.setHeader("Content-Type", "application/json");
const resData = { data };
res.end(JSON.stringify(resData));
});
server.middlewares.use("/addUser", async (req, res) => {
let body = "";
req.on("data", (chunk) => {
body += chunk.toString();
});
req.on("end", () => {
data.push(JSON.parse(body));
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({ code: 0, echo: body }));
});
});
},
};
};
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), vitePluginServer()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});4 种导航方式
<Link />:<Link />组件会被渲染为<a>标签, 并且阻止了<a>标签点击事件的默认行为, 不会重新加载页面<NavLink />:<NavLink />属性和<Link />属性相同- 编程式导航
useNavigate - 重定向
redirect
<Link />
- to 导航的目的路径
- replace
replace={false}默认, 不替换当前路径, 保留历史记录, 反映在浏览器的前进/后退按钮 (history.pushState)replace={true}替换当前路径, 不保留历史记录, 反映在浏览器的前进/后退按钮 (history.replaceState)
- state 参考路由传参, state 传递参数
- relative
- 例如 3 条路径 /layout, /layout/home, /layout/about
relative="route"默认, 必须使用绝对路径- 例如当前路径
/layout/home - 目的路径
/layout,/layout/about
- 例如当前路径
relative="path"可以使用相对路径- 例如当前路径
/layout/home - 目标路径
../,../about
- 例如当前路径
- reloadDocument 页面跳转时, 是否重新加载页面
- preventScrollReset 是否阻止滚动位置重置
- viewTransition 页面跳转时, 是否开启 opacity 过渡
<NavLink />
<NavLink /> 属性和 <Link /> 属性相同
不同: 路由导航时, <NavLink /> 会经过 3 个状态的转换, <Link /> 不会
- active 激活状态, 当前路径和目的路径匹配
- pending 等待状态, 等待 loader 加载数据, 参考路由操作: loader
- transitioning 过渡状态, 需要使用 viewTransition 属性开启 opacity 过渡
/* 激活状态时, react-router 自动添加类名 active */
a.active {
}
/* 等待状态时, react-router 自动添加类名 pending */
a.pending {
}
/* 过渡状态时, react-router 自动添加类名 transitioning */
a.transitioning {
}也可以使用 style 属性
<NavLink
to="/about"
viewTransition
style={({ isActive, isPending, isTransitioning }) => {
return {
color: (() => {
if (isActive) {
return "#ff000088";
}
if (isPending) {
return "#00ff0088";
}
if (isTransitioning) {
return "#0000ff88";
}
return "#000";
})(), // IIFE
};
}}
>
about
</NavLink>useNavigate
import { useNavigate } from "react-router";
const navigate = useNavigate();
navigate(
"/home",
{
replace: false, // 默认, 不替换当前路径, 保留历史记录
state: { love: "you" }, // 参考路由传参, state 传递参数
relative: "route", // 默认, 必须使用绝对路径
preventScrollReset: false, // 不阻止滚动位置重置
viewTransition: true, // 页面跳转时, 开启 opacity 过渡
} /** options */,
);redirect
需要配合 loader 使用
@/router/index.tsx
@/router/index.tsx
import App from "@/App";
import { lazy } from "react";
import { createBrowserRouter, redirect } from "react-router";
const getToken = () => {
return new Promise((resolve) => {
setTimeout(
() => resolve(Math.random() * 10 > 5 ? "I love you" : null),
3000,
);
});
};
export const router = createBrowserRouter([
{
path: "/",
Component: App,
children: [
{
path: "/home",
Component: lazy(() => import("@/pages/Home")),
},
{
path: "/about",
Component: lazy(() => import("@/pages/About")),
loader: async () => {
const token = await getToken();
if (!token) {
return redirect("/home");
}
return { token };
},
},
],
},
]);Last updated on