import * as React from "react";
import { createRoot } from "react-dom/client";
import {
createBrowserRouter,
RouterProvider,
Route,
Link,
} from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: (
<div>
<h1>Hello World</h1>
<Link to="about">About Us</Link>
</div>
),
},
{
path: "about",
element: <div>About</div>,
},
]);
createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
);
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";
import Root, { rootLoader } from "./routes/root";
import Team, { teamLoader } from "./routes/team";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
path: "team",
element: <Team />,
loader: teamLoader,
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
);
function createBrowserRouter(
routes: RouteObject[],
opts?: {
basename?: string;
future?: FutureConfig;
hydrationData?: HydrationState;
window?: Window;
}
): RemixRouter;
An array of Route objects with nested routes on the children property.
createBrowserRouter([
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
path: "events/:id",
element: <Event />,
loader: eventLoader,
},
],
},
]);
Route定义为:
const router = createBrowserRouter([
{
// it renders this element
element: <Team />,
// when the URL matches this segment
path: "teams/:teamId",
// with this data loaded before rendering
loader: async ({ request, params }) => {
return fetch(
`/fake/api/teams/${params.teamId}.json`,
{ signal: request.signal }
);
},
// performing this mutation when data is submitted to it
action: async ({ request }) => {
return updateFakeTeam(await request.formData());
},
// and renders this element in case something went wrong
errorElement: <ErrorBoundary />,
},
]);
RouteObject对象的声明为:
interface RouteObject {
path?: string;
index?: boolean;
children?: React.ReactNode;
caseSensitive?: boolean;
id?: string;
loader?: LoaderFunction;
action?: ActionFunction;
element?: React.ReactNode | null;
Component?: React.ComponentType | null;
errorElement?: React.ReactNode | null;
ErrorBoundary?: React.ComponentType | null;
handle?: RouteObject["handle"];
shouldRevalidate?: ShouldRevalidateFunction;
lazy?: LazyRouteFunction<RouteObject>;
}
对于Path:
动态路由参数, 可以在loader, action中通过params直接点出 或者使用useParams()钩子来获取该参数
<Route
// this path will match URLs like
// - /teams/hotspur
// - /teams/real
path="/teams/:teamId"
// the matching param will be available to the loader
loader={({ params }) => {
console.log(params.teamId); // "hotspur"
}}
// and the action
action={({ params }) => {}}
element={<Team />}
/>;
// and the element through `useParams`
function Team() {
let params = useParams();
console.log(params.teamId); // "hotspur"
}
你可以定义多个动态参数 都可以通过 . 运算符获取到
<Route path="/c/:categoryId/p/:productId" />;
// both will be available
params.categoryId;
params.productId;
可选参数 通过在后面加上 ?
<Route
// this path will match URLs like
// - /categories
// - /en/categories
// - /fr/categories
path="/:lang?/categories"
// the matching param might be available to the loader
loader={({ params }) => {
console.log(params["lang"]); // "en"
}}
// and the action
action={({ params }) => {}}
element={<Categories />}
/>;
// and the element through `useParams`
function Categories() {
let params = useParams();
console.log(params.lang);
}
当然可以同时用动态参数和可选参数
模糊匹配所有的 * 参数
<Route
// this path will match URLs like
// - /files
// - /files/one
// - /files/one/two
// - /files/one/two/three
path="/files/*"
// the matching param will be available to the loader
loader={({ params }) => {
console.log(params["*"]); // "one/two"
}}
// and the action
action={({ params }) => {}}
element={<Team />}
/>;
// and the element through `useParams`
function Team() {
let params = useParams();
console.log(params["*"]); // "one/two"
}
布局路由
<Route
element={
<div>
<h1>Layout</h1>
<Outlet />
</div>
}
>
<Route path="/" element={<h2>Home</h2>} />
<Route path="/about" element={<h2>About</h2>} />
</Route>
组件留出子路由要渲染的位置,仅此而已index
索引路由 url为父路径时显示的组件
}>
} />
} />
有一个路径path
为"/teams",并渲染了一个组件
。index
属性的嵌套的
。这意味着当路径匹配"/teams"时,组件
将被呈现到其父级路由的Outlet
中(在这种情况下是
)。path=":teamId"
的嵌套路由,表示当路径匹配"/teams/:teamId"时,将呈现组件
。children: 嵌套路由
caseSensitive: 表示对路径中大小写敏感
Instructs the route to match case or not:
<Route caseSensitive path="/wEll-aCtuA11y" />
loader
每一个路由都能定义一个loader函数来为路由组件渲染之前提供数据
createBrowserRouter([
{
element: <Teams />,
path: "teams",
loader: async () => {
return fakeDb.from("teams").select("*");
},
children: [
{
element: <Team />,
path: ":teamId",
loader: async ({ params }) => {
return fetch(`/api/teams/${params.teamId}.json`);
},
},
],
},
]);
通过 调用loader函数获得的数据可以通过 useLoaderData钩子拿到useLoaderData
: This hook provides the value returned from your route loader.
import {
createBrowserRouter,
RouterProvider,
useLoaderData,
} from "react-router-dom";
function loader() {
return fetchFakeAlbums();
}
export function Albums() {
const albums = useLoaderData();
// ...
}
const router = createBrowserRouter([
{
path: "/",
loader: loader,
element: <Albums />,
},
]);
ReactDOM.createRoot(el).render(
<RouterProvider router={router} />
);
在调用路由操作之后,数据将自动重新验证,并从加载器返回最新的结果。
请注意,useLoaderData
不会启动抓取。它只是读取 React Router 在内部管理的抓取结果,因此您不必担心在重新呈现的原因之外的情况下重新获取数据。
这也意味着在重新呈现之间返回的数据是稳定的,因此您可以安全地将其传递给 React hooks(如 useEffect)中的依赖数组。它只在再次调用加载器后(在执行操作或特定导航后)才会更改。在这些情况下,标识将更改(即使值没有更改)。
您可以在任何组件或任何自定义 hook 中使用此钩子,而不仅仅是在 Route 元素中。它将返回上下文中最近路由的数据。params
: 由url中解析的动态参数会放在params中并传递给loader函数
createBrowserRouter([
{
path: "/teams/:teamId",
loader: ({ params }) => {
return fakeGetTeam(params.teamId);
},
},
]);
request
:
<a
href={props.to}
onClick={(event) => {
event.preventDefault();
navigate(props.to);
}}
/>
React router阻止浏览器将请求request发送给server, 而是转给了loader函数
function loader({ request }) {
const url = new URL(request.url);
const searchTerm = url.searchParams.get("q");
return searchProducts(searchTerm);
}
返回 response
可以返回fetch()发送异步请求(ajax, axios)的结果,也可以自定义返回结果
// an HTTP/REST API
function loader({ request }) {
return fetch("/api/teams.json", {
signal: request.signal,
});
}
// or even a graphql endpoint
function loader({ request, params }) {
return fetch("/_gql", {
signal: request.signal,
method: "post",
body: JSON.stringify({
query: gql`...`,
params: params,
}),
});
}
function loader({ request, params }) {
const data = { some: "thing" };
return new Response(JSON.stringify(data), {
status: 200,
headers: {
"Content-Type": "application/json; utf-8",
},
});
}
react router会自动调用 response.json()
function SomeRoute() {
const data = useLoaderData();
// { some: "thing" }
}
在loader中抛出异常
function loader({ request, params }) {
const res = await fetch(`/api/properties/${params.id}`);
if (res.status === 404) {
throw new Response("Not Found", { status: 404 });
}
return res.json();
}
会渲染路由中定义的 errorElement组件action
: 进行写操作的函数
<Route
path="/song/:songId/edit"
element={<EditSong />}
action={async ({ params, request }) => {
let formData = await request.formData();
return fakeUpdateSong(params.songId, formData);
}}
loader={({ params }) => {
return fakeGetSong(params.songId);
}}
/>
在应用发起一个非get请求时被调用 比如按钮点击
// forms
<Form method="post" action="/songs" />;
<fetcher.Form method="put" action="/songs/123/edit" />;
// imperative submissions
let submit = useSubmit();
submit(data, {
method: "delete",
action: "/songs/123",
});
fetcher.submit(data, {
method: "patch",
action: "/songs/123/edit",
});
element / Component: 要渲染的组件
errorElement / ErrorBoundary: 异常时渲染的组件
当在loader, action函数执行或者组件渲染时抛出异常, 就不会渲染element组件, 而是用error path的组件 errorElement渲染, 该异常可以用 useRouteError获取
lazy :
let routes = createRoutesFromElements(
<Route path="/" element={<Layout />}>
<Route path="a" lazy={() => import("./a")} />
<Route path="b" lazy={() => import("./b")} />
</Route>
);
The basename of the app for situations where you can’t deploy to the root of the domain, but a sub directory.
createBrowserRouter(routes, {
basename: "/app",
});
All data router objects are passed to this component to render your app and enable the rest of the data APIs
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
children: [
{
path: "dashboard",
element: <Dashboard />,
},
{
path: "about",
element: <About />,
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<RouterProvider
router={router}
fallbackElement={<BigSpinner />}
/>
);
createBrowserRouter
函数的一个特性:在非服务器端渲染应用程序时,当它挂载时会初始化所有匹配的路由加载器。在此期间,你可以提供 fallbackElement
,以向用户显示应用程序正在加载的指示。这个特性的目的是在加载路由数据时,提供一个加载过程中的占位元素(fallback element),使用户在等待加载完成时能够看到一些界面反馈,而不是一片空白。
举例来说,你可以在 createBrowserRouter
中添加 fallbackElement
选项,像这样:
createBrowserRouter(routes, {
fallbackElement: <div>Loading...</div>,
});
这样,当路由加载的过程中,页面将显示 “Loading…”,以告诉用户应用程序正在处理数据,避免用户在加载过程中感到不确定或无反馈。
import { Await, useLoaderData } from "react-router-dom";
function Book() {
const { book, reviews } = useLoaderData();
return (
<div>
<h1>{book.title}</h1>
<p>{book.description}</p>
<React.Suspense fallback={<ReviewsSkeleton />}>
<Await
resolve={reviews}
errorElement={
<div>Could not load reviews </div>
}
children={(resolvedReviews) => (
<Reviews items={resolvedReviews} />
)}
/>
</React.Suspense>
</div>
);
}
这段代码是一个使用 React Router 的 useLoaderData
和 Await
组件的 React 组件。让我解释一下它的主要部分:
useLoaderData
是 React Router 提供的一个 hook,用于获取路由加载器(loader)返回的数据。在这里,通过 useLoaderData
获取了 book
和 reviews
。return
中,首先渲染了 book
的标题和描述。Suspense
组件,它接受 fallback
属性,指定在子组件加载过程中显示的占位元素。在这里,如果 reviews
尚未加载完成,将显示一个 ReviewsSkeleton
组件,用作加载占位符。Await
组件包装了 reviews
的加载。Await
组件接受 resolve
属性,指定需要等待的 Promise。如果 resolve
Promise 处于 pending 状态,它会渲染 fallback
中的占位元素;如果 Promise 处于 resolved 状态,它会调用 children
函数,并将 resolved 的数据传递给它。children
函数中,渲染了一个 Reviews
组件,传递了从 reviews
获取的 resolved 数据。总体来说,这段代码的目的是展示书籍的标题和描述,同时以异步方式加载书籍的评论。在加载评论的过程中,会显示一个加载中的占位符,确保用户在等待数据加载完成时能够看到一些界面反馈。如果加载评论失败,将显示一个错误提示。