createBrowserRouter
这是所有 React Router Web 项目推荐的路由器。它使用 DOM History API 来更新 URL 并管理历史堆栈。
它还支持 v6.4 数据 API,例如 加载器、操作、获取器 等等。
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;
routes
一个 Route
对象数组,在 children
属性上嵌套路由。
createBrowserRouter([
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
path: "events/:id",
element: <Event />,
loader: eventLoader,
},
],
},
]);
basename
应用程序的基路径,用于您无法部署到域根目录,而需要部署到子目录的情况。
createBrowserRouter(routes, {
basename: "/app",
});
尾部斜杠将在链接到根目录时被保留。
createBrowserRouter(routes, {
basename: "/app",
});
<Link to="/" />; // results in <a href="/app" />
createBrowserRouter(routes, {
basename: "/app/",
});
<Link to="/" />; // results in <a href="/app/" />
future
此路由器可以选择启用一组可选的 未来标志。我们建议尽早启用新发布的未来标志,以便更轻松地迁移到 v7。
const router = createBrowserRouter(routes, {
future: {
// Normalize `useNavigation()`/`useFetcher()` `formMethod` to uppercase
v7_normalizeFormMethod: true,
},
});
目前提供以下未来标志:
标志 | 描述 |
---|---|
v7_fetcherPersist |
延迟活动 fetcher 清理,直到它们返回到 idle 状态。 |
v7_normalizeFormMethod |
将 useNavigation().formMethod 规范化为大写 HTTP 方法。 |
v7_partialHydration |
支持服务器渲染应用程序的局部水合。 |
v7_prependBasename |
将路由器基本名称附加到导航/获取路径。 |
v7_relativeSplatPath |
修复 splat 路由中相对路径解析的错误。 |
unstable_skipActionErrorRevalidation |
如果操作返回 4xx/5xx Response ,则默认情况下不会重新验证。 |
hydrationData
当 服务器渲染 并且 选择退出自动水合 时,hydrationData
选项允许您从服务器渲染中传入水合数据。这几乎总是从 handler.query 返回的 StaticHandlerContext
值中获取的数据子集。
const router = createBrowserRouter(routes, {
hydrationData: {
loaderData: {
// [routeId]: serverLoaderData
},
// may also include `errors` and/or `actionData`
},
});
您几乎总是会包含一组完整的 loaderData
来水合服务器渲染的应用程序。但在高级用例(例如 Remix 的 clientLoader
)中,您可能希望只包含某些在服务器上渲染的路由的 loaderData
。如果您想启用局部 loaderData
并选择加入粒度 route.HydrateFallback
使用,则需要启用 future.v7_partialHydration
标志。在此标志之前,任何提供的 loaderData
都被认为是完整的,并且不会导致在初始水合时执行路由加载器。
当指定此标志时,加载器将在初始水合时在两种情况下运行:
HydrateFallback
组件将在初始水合时渲染。loader.hydrate
属性设置为 true
。loader
,即使您没有在初始水合时渲染回退(即,使用水合数据来初始化缓存)。const router = createBrowserRouter(
[
{
id: "root",
loader: rootLoader,
Component: Root,
children: [
{
id: "index",
loader: indexLoader,
HydrateFallback: IndexSkeleton,
Component: Index,
},
],
},
],
{
future: {
v7_partialHydration: true,
},
hydrationData: {
loaderData: {
root: "ROOT DATA",
// No index data provided
},
},
}
);
unstable_dataStrategy
loader
/action
执行的内部处理,如果操作不当,会导致应用程序代码出现故障。请谨慎使用并进行适当的测试。
默认情况下,React Router 对数据加载/提交的方式持有一定意见——最值得注意的是,它会并行执行所有加载器,以实现最佳的数据获取。虽然我们认为这对于大多数用例来说是正确的行为,但我们意识到,对于各种应用程序需求而言,没有“一刀切”的数据获取解决方案。
unstable_dataStrategy
选项使您可以完全控制加载器和操作的执行方式,并为构建更高级的 API(例如中间件、上下文和缓存层)奠定了基础。随着时间的推移,我们预计将在内部利用此 API 为 React Router 提供更多一流的 API,但在那之前(以及之后),这是您为应用程序数据需求添加更高级功能的方式。
interface DataStrategyFunction {
(args: DataStrategyFunctionArgs): Promise<
HandlerResult[]
>;
}
interface DataStrategyFunctionArgs<Context = any> {
request: Request;
params: Params;
context?: Context;
matches: DataStrategyMatch[];
}
interface DataStrategyMatch
extends AgnosticRouteMatch<
string,
AgnosticDataRouteObject
> {
shouldLoad: boolean;
resolve: (
handlerOverride?: (
handler: (ctx?: unknown) => DataFunctionReturnValue
) => Promise<HandlerResult>
) => Promise<HandlerResult>;
}
interface HandlerResult {
type: "data" | "error";
result: any; // data, Error, Response, DeferredData
status?: number;
}
unstable_dataStrategy
接收与 loader
/action
相同的参数(request
、params
),但它还接收一个 matches
数组,该数组是匹配路由的数组,其中每个匹配都扩展了两个新字段,用于在数据策略函数中使用。
match.resolve
- 一个异步函数,它将解析任何 route.lazy
实现并执行路由的处理程序(如果需要),返回一个 HandlerResult
。match.resolve
,以确保所有延迟路由都正确解析。resolve
仅在需要时以及如果您没有传递自己的 handlerOverride
函数参数时才会在内部调用 handler
。match.resolve
实现自定义处理程序执行。match.shouldLoad
- 一个布尔值,指示此路由处理程序是否需要在此传递中调用。matches
数组始终包含所有匹配的路由,即使只有某些路由处理程序需要调用,以便可以实现诸如中间件之类的功能。shouldLoad
通常只有在您完全跳过路由处理程序并实现自定义处理程序逻辑时才有趣——因为它可以让您确定是否应该为此路由运行该自定义逻辑。/parent/child/a
并导航到 /parent/child/b
——您将获得一个包含三个匹配项的数组([parent, child, b]
),但只有 b
将具有 shouldLoad=true
,因为 parent
和 child
的数据已经加载。/parent/child/a
并提交到 a
的 action
,那么只有 a
将具有 shouldLoad=true
,用于 dataStrategy
的操作执行。loader
重新验证调用 dataStrategy
,并且所有匹配项都将具有 shouldLoad=true
(假设没有自定义 shouldRevalidate
实现)。dataStrategy
函数应返回一个并行的 HandlerResult
实例数组,指示处理程序是否成功。如果返回的 handlerResult.result
是一个 Response
,React Router 将为您解开它(通过 res.json
或 res.text
)。如果您需要对 Response
进行自定义解码,但保留状态代码,则可以在 handlerResult.result
中返回解码后的值,并通过 handlerResult.status
发送状态(例如,当使用 future.unstable_skipActionRevalidation
标志时)。如果您没有传递处理程序覆盖函数,则 match.resolve()
将返回一个 HandlerResult
。如果您传递了处理程序覆盖函数,那么您需要将 handler
结果包装在 HandlerResult
中(请参阅下面的示例)。
在最简单的情况下,让我们看看如何挂钩到此 API 以添加一些日志记录,以记录路由加载器/操作执行的时间。
let router = createBrowserRouter(routes, {
unstable_dataStrategy({ request, matches }) {
return Promise.all(
matches.map(async (match) => {
console.log(`Processing route ${match.route.id}`);
// Don't override anything - just resolve route.lazy + call loader
let result = await match.resolve();
console.log(
`Done processing route ${match.route.id}`
);
return result;
})
);
},
});
让我们通过 handle
在每个路由上定义一个中间件,并首先按顺序调用中间件,然后并行调用所有加载器——提供通过中间件提供的任何数据。
const routes = [
{
id: "parent",
path: "/parent",
loader({ request }, context) {
/*...*/
},
handle: {
async middleware({ request }, context) {
context.parent = "PARENT MIDDLEWARE";
},
},
children: [
{
id: "child",
path: "child",
loader({ request }, context) {
/*...*/
},
handle: {
async middleware({ request }, context) {
context.child = "CHILD MIDDLEWARE";
},
},
},
],
},
];
let router = createBrowserRouter(routes, {
async unstable_dataStrategy({
request,
params,
matches,
}) {
// Run middleware sequentially and let them add data to `context`
let context = {};
for (const match of matches) {
if (match.route.handle?.middleware) {
await match.route.handle.middleware(
{ request, params },
context
);
}
}
// Run loaders in parallel with the `context` value
return Promise.all(
matches.map((match, i) =>
match.resolve(async (handler) => {
// Whatever you pass to `handler` will be passed as the 2nd parameter
// to your loader/action
let result = await handler(context);
return { type: "data", result };
})
)
);
},
});
您甚至可能不想在路由级别定义加载器实现。也许您只想确定路由并为所有数据发出单个 GraphQL 请求?您可以通过将 route.loader=true
设置为使其符合“具有加载器”的条件,然后将 GQL 片段存储在 route.handle
上来实现。
const routes = [
{
id: "parent",
path: "/parent",
loader: true,
handle: {
gql: gql`
fragment Parent on Whatever {
parentField
}
`,
},
children: [
{
id: "child",
path: "child",
loader: true,
handle: {
gql: gql`
fragment Child on Whatever {
childField
}
`,
},
},
],
},
];
let router = createBrowserRouter(routes, {
unstable_dataStrategy({ request, params, matches }) {
// Compose route fragments into a single GQL payload
let gql = getFragmentsFromRouteHandles(matches);
let data = await fetchGql(gql);
// Parse results back out into individual route level HandlerResult's
let results = parseResultsFromGql(data);
return results;
},
});
window
对于浏览器开发者工具插件或测试等环境很有用,可以使用与全局 window
不同的窗口。