Remix 开发小技巧(二)

由于我计划将来从事其他 Remix 项目,我想写几篇博文作为自己的指南。这是一项正在进行的工作,我将在学习新事物时对其进行更新。

创建自定义 Remix Hook 访问来自任何组件的加载器数据

访问加载器数据的典型方法是 useLoaderData 钩子,它将获取最近的加载器的数据。这是通过 React Context 实现的,因此您也可以在组件中使用它。

这里有一把脚枪:如果您尝试在不同的路由中重用该组件,它将获取该路由的加载器数据。

为了解决这个问题,Remix 提供了一个 useRouteLoaderData 钩子,让你通过传递其 ID 来获取任何活动路由的加载器数据。请务必注意,这不会导致任何新的加载程序运行,它只是获取数据(如果可用)。

从任何地方访问 Root 加载程序数据

根路由始终处于活动状态,因此它是存储您希望在任何地方可用的数据的好地方。

您可以使用它从任何路由访问根加载程序数据,但如果需要类型安全,则需要导入根加载程序的类型并将其作为类型参数传递给 useRouteLoaderData

// some-other-route.tsx
import type { loader as rootLoader } from "./root.tsx"
export default function SomeOtherRoute() {
  const rootLoaderData =
    useRouteLoaderData<typeof rootLoader>("root")
  // …
}

我不是这种方法的忠实粉丝,因为我每次使用它时都必须导入加载器(通常带有别名)并记下路由 ID。虽然根的 ID 始终 “root” 是 ,但其他路由具有不同的 ID。在实践中,t 很容易在 ID 和加载器类型之间出现不匹配。

与其这样做,我更喜欢在我的 root.tsx 文件中创建一个自定义钩子,其中加载器已经可用,并将其导出以供其他文件使用。

// root.tsx
export async function loader() {}

export function useRootLoaderData() {
  return useRouteLoaderData<typeof loader>("root")
}

查找所需路由的 ID

根据您使用的路由约定,特定路由的 ID 可以是任何内容。与其试图记住模式或挖掘清单,我喜欢使用 useMatches() 钩子。

由于 useMatch 返回所有活动路由的数组,因此它还形成了一个完整的列表,其中包含 useRouteLoaderData 将在给定页面上使用的路由。

// some-other-route.tsx
import { useRootLoaderData } from "./root.tsx"
export default function SomeOtherRoute() {
  // temporary just to discover the route IDs
  const matches = useMatches()
  console.log(matches)
  // [
  //   {
  //     id: 'root',
  //     pathname: '/',
  //     data: [Object],
  //   },
  //   {
  //     id: '_layout',
  //     pathname: '/',
  //     data: [Object],
  //   },
  //   {
  //     id: '_layout.content',
  //     pathname: '/content',
  //     data: null,
  //   },
  //   {
  //     id: '_layout.content.$slug',
  //     pathname: '/content/remix-route-loader-data',
  //     data: [Object],
  //   }
  // ]
}

使用中的第一个路由始终是根路由,最后一个路由始终是当前路由。当你这样做时,你通常会寻找中间的那些。在本例中,您可以看到布局路由的 ID 为 _layout ,因此我将使用这种 ID。

知道 ID 后,可以删除此代码。

如果路由没有加载程序,则数据将为 null,但如果尝试访问未处于活动状态的路由,则数据将未定义。这几乎总是开发人员的错误(在错误的位置使用组件),因此,如果未定义,最好抛出错误。

非根路由加载程序数据

// routes/_layout.tsx
export async function loader() {}
export function useLayoutLoaderData() {
  const data = useRouteLoaderData<typeof loader>("_layout")
  if (data === undefined) {
    throw new Error(
      "useLayoutLoaderData must be used within the _layout route or its children",
    )
  }
  return data
}

根路由加载程序数据

// root.tsx
export async function loader() {}
export function useRootLoaderData() {
  return useRouteLoaderData<typeof loader>("root")
}

定义 404

首先,让我们介绍所有网页应用程序中最常见的错误之一,404 Not Found。

您需要做几件事:

  • 在路由文件夹的根目录下创建一个新文件 $.tsx 对于找不到其路由的 URL,这实际上是一个包罗万象的。
  • 在加载器中使用 404 进行响应 Remix 还不知道您正在使用此包罗万象的路由来处理 404 错误,因此它不会像搜索引擎所期望的那样使用 http 状态代码 404 进行响应。因此,添加一个加载程序并使用 404 进行响应。
export function loader() {
  return new Response("Not Found", {
    status: 404,
  });
}
  • 定义您的页面:我正在使用一个简单的组件来显示错误,但如果您愿意,您可以使您的页面更好。
export default function NotFoundPage() {
  return <BigStatusMessage
    type="error"
    message="Not Found"
    title="404"
   />;
}

定义根错误边界

这是最后的手段错误边界,当不存在其他错误边界时,将针对错误触发它。您应该拥有的不仅仅是根错误边界。

// root.tsx
export function ErrorBoundary() {
  const error = useRouteError();
  return (
    <html>
      <head>
        <title>Oh no!</title>
        <Meta />
        <Links />
      </head>
      <body>
        {/* add the UI you want your users to see */}
        <Scripts />
      </body>
    </html>
  );
}

使用 isRouteErrorResponse 检查它是否为 HTTP 错误响应。例如,您可以使用它来检查 error.status。
 
isRouteErrorResponse 将为加载程序和操作中抛出的响应返回 true。请注意,在我们的 404 页面加载器中,我们只是简单地返回响应而不是抛出它?

处理其他错误

关于如何以及在何处放置其他错误边界的文章已经写了很多,所以我会保持这一点。但我的经验法则是:

您不需要为每个方案使用错误边界

无论在哪里定义 ,都应该定义边界

请记住,只有当出现加载程序错误时,才会在服务器上呈现 ErrorBound。如果存在操作错误,则仅在客户端上呈现该错误。为什么这很重要?如果要天真地捕获和跟踪边界中的错误,则需要实现一些逻辑来确定错误类型。

不要抛出输入错误

首先,您不希望使用 ErrorBoundary 处理输入错误。理想情况下,您希望在用户输入旁边处理它们。

其次,抛出错误将触发handleError,如果您设置了跟踪,则将向错误跟踪服务报告所有输入错误。我向你保证,你不需要知道什么时候有人忘记输入有效的电子邮件,如果你真的需要它,它可能应该是分析的一部分。

对用户体验的一条建议

每当您显示错误时,请为用户提供有关问题所在以及如何解决错误的指导。

例如,不要只显示“出了点问题”。而是显示类似以下内容:“找不到页面<返回主页>”

在这里插入图片描述

如果你已经做到了这一点,希望你的错误处理现在处于更好的状态。

创建可重用的表单组件

假设我们要为我们的应用程序构建一个自定义 Form 组件,这个 Form 将在内部执行一些操作,例如使用 CSRF 令牌呈现隐藏的输入,接受重定向以在正文中发送,或者只是应用所有表单都将具有的一些样式。

构建这样的东西的第一种方法可能是包装 Remix 的 Form 组件。

import type { FormProps as RemixFormProps } from "@remix-run/react";

import { Form as RemixForm } from "@remix-run/react";

type FormProps = RemixFormProps & {
  redirectTo?: string;
};

export function Form({ redirectTo, children, ...props }: FormProps) {
  return (
    <RemixForm {...props} className="some classes">
      {redirectTo && (
        <input type="hidden" name="redirectTo" value={redirectTo} />
      )}
      <CSRFTokenInput />
      {children}
    </RemixForm>
  );
}

这将起作用,直到您想用 fetcher.Form .为了解决这个问题,我们可以接受组件作为 prop!

type FormProps = RemixFormProps & {
  redirectTo?: string;
  form?: React.ComponentType<FormProps>;
};

export function Form({
  redirectTo,
  children,
  // here we default to RemixForm
  form: Component = RemixForm,
  ...props
}: FormProps) {
  return (
    <Component {...props} className="some classes">
      {/* content here */}
    </Component>
  );
}

最后,我们可以在路由或任何其他组件中使用它:

function Route() {
  let fetcher = useFetcher();

  return (
    <>
      <Form />; // with the default
      <Form form={fetcher.Form} />; // with a fetcher
    </>
  );
}

此模式与 remix-form 用于让您传递 Form 或提取器的模式相同。表单,来自 Remix 或 React Router。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Willin 老王带你躺平养老

感谢你这么好看还这么慷慨

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值