免费商城网站建站系统,旅游网站开发团队,seo关键词挖掘工具,免费网络电话免费30分钟文章目录 类型安全的 Fetcher 钩子一切从资源路由开始RPC 只是使用内置的 URL 获取使用 Zod 验证您的 RPC下一步是自定义提取器钩子 黑暗模式主题切换“最佳用户体验”是什么意思#xff1f;第一个要求第二个要求第三个要求第四个要求 类型安全的 Fetcher 钩子
RPC 是一种远程… 文章目录 类型安全的 Fetcher 钩子一切从资源路由开始RPC 只是使用内置的 URL 获取使用 Zod 验证您的 RPC下一步是自定义提取器钩子 黑暗模式主题切换“最佳用户体验”是什么意思第一个要求第二个要求第三个要求第四个要求 类型安全的 Fetcher 钩子
RPC 是一种远程过程调用这是一种奇特的说法表示“在服务器上运行的函数”。
他们现在正在经历一个鼎盛时期gRPC、tRPC 和 Next.js Server Actions 等工具越来越受欢迎并重新激发了对该模式的兴趣。
但我不建议将它们与 Remix 一起使用。
Remix 的工作方式与典型的 Web 框架略有不同。它的设计重点是渐进式增强和利用浏览器的强大功能。
通过使用 RPC 库您将远离这些好处。
例如不能使用 tRPC 路由器生成与基本 HTML 表单兼容的 Endpoints。
在 Next.js 服务器操作宣布之前Next 框架从未真正承认数据突变是一回事。由于没有内置的支持tRPC 非常适合该利基市场两者成为开发的绝佳组合。
通过在编写 Remix 应用程序的方式中采用一些新习惯您可以在不牺牲 Remix 优势的情况下获得 RPC 的好处。
一切从资源路由开始
Remix 源于 React Router路由是它所说的语言。Remix 应用是通过创建路由来获取数据、处理突变、提供文件、呈现页面等来构建的。
在单个文件中任何页面都可以通过指定操作函数成为 POST 端点。
export async function action({ request }: ActionArgs) {const body await request.formData()const title body.get(title)if (!title) {throw new Response(Title is required, { status: 400 })}const description body.get(description)const item db.create({title: title.toString(),description: description?.toString(),})return json(item, { status: 201 })
}或者它可以通过指定加载程序函数成为 GET 终结点。
export async function loader({ params }: LoaderArgs) {const item db.get(params.id)if (item) {return json(item, { status: 200 })}throw new Response(Not found, { status: 404 })
}这些函数的终结点 URL 是根据文件路径自动生成的。要调用这些函数任何组件都需要知道它要调用的资源路由的 URL然后它可以向该 URL 发出请求。
下面是一些以编程方式调用上一个 POST 终结点的客户端代码。
const body new FormData()
body.append(title, title)
body.append(description, description)
const response await fetch(/items, {method: POST,body,
})由于几个原因这并不完全理想
URL 是硬编码的因此如果 URL 发生更改您必须在使用它的所有位置更新它您无法知道端点需要哪些参数你无法知道响应会是什么样子
这就是 RPC 模式的用武之地
RPC 只是使用内置的 URL 获取
Web 应用程序通过在客户端和服务器之间发送 HTTP 请求来工作。
大多数如果不是全部专用 RPC 库的运行方式相同。它们只是抽象出HTTP请求和响应的细节并为您提供一个不错的API。
我们可以自己做以前面的请求为例并将其包装在一个函数中。
我们可以使用 Typescript 来定义一个 Item 类型该类型与我们传入的参数以及我们期望的响应相匹配。
type Item {id: stringtitle: stringdescription?: string
}
export async function createItem(item: OmitItem, id,
): Item {const body new FormData()body.append(title, item.title)body.append(description, item.description)const response await fetch(/items, {method: POST,body,})if (!response.ok) {throw new Error(Failed to create item)}const createdItem await response.json()if (!createdItem.id || !createdItem.title) {throw new Error(Invalid response)}return createdItem
}如果从资源路由导出该函数则可以在应用中的任何位置使用它并获得完整的端到端类型安全性和自动完成功能。
import { createItem } from ~/routes/items.server使用 Zod 验证您的 RPC
手动验证可能会很痛苦尤其是当类型变得更加复杂时。幸运的是有一个库
您可以使用 Zod 和 zod-form-data 在 RPC 和操作函数中验证表单数据。
import { z } from zod
import { zfd } from zod-form-data
const itemSchema zfd.formData({title: z.string().min(1),description: z.string().optional(),
})
export async function action({ request }: ActionArgs) {const body itemSchema.parse(await request.formData())const item db.create({title: body.title,description: body.description,})return json(item, { status: 201 })
}
export async function createItem(item: z.inferitemSchema,
) {const body new FormData()body.append(title, item.title)body.append(description, item.description)const response await fetch(/items, {method: POST,body,})if (!response.ok) {throw new Error(Failed to create item)}const createdItem await response.json()return itemSchema.parse(createdItem)
}现在您可以在客户端和服务器中使用相同的验证并且可以确信要发送和接收的数据是有效的。
下一步是自定义提取器钩子
如果您尝试调用的终端节点影响加载程序使用的数据您可能不希望只对其进行常规提取调用。
Remix 的 useFetcher 钩子有很多你想要利用的生活质量功能例如
自动重新获取装载机重复请求取消避免具有多个请求的争用条件如果服务器返回重定向响应则重定向客户端
因此为了在这里正确使用它我们可以在模式中采用创建一个自定义的类型安全获取器钩子我们可以在应用程序中的任何位置使用它。
export async function useSubmitItem() {const fetcher useFetcher()const submit useCallback((item: z.inferitemSchema) {const body new FormData()body.append(title, item.title)body.append(description, item.description)fetcher.submit(body, {method: POST,action: /items,})},[fetcher],)return submit
}这是使我们与 tRPC 等解决方案具有平价功能缺失的部分。
它感觉不像一个 RPC更像是一个自定义钩子但用法是相同的
每个资源路由导出客户端可以调用以与服务器交互的函数客户端与服务器交互的主要方式是通过这些功能当服务器上的类型更新时客户端将收到类型错误直到它更新其函数的使用
此外您还可以获得 RPC 库无法提供的好处例如
对本机表单和表单组件的开箱即用支持服务器代码与客户端代码的共置因此您不需要定义所有 RPC 函数的中央路由器文件
黑暗模式主题切换
今天多亏了像Tailwind这样的工具我们可以轻松地在我们的应用程序中实现暗模式。现在通过此功能暗模式寻求最佳用户体验是另一回事。这就是 Remix 的亮点让您完全控制从后端到前端的用户体验。
“最佳用户体验”是什么意思
为了获得更好的暗模式体验我认为这是个人意见的要求是
用户首次访问页面时服务器必须以深色或浅色模式发送页面具体取决于用户当时的计算机设置。 否则用户将在应用程序中遇到闪光这是因为服务器最初发送具有一个主题的页面但随后应用程序在用户的计算机上检测到不同的主题并进行切换。如下图所示 如果用户未选择任何模式则当用户更改其计算机的模式时页面将切换到深色或浅色模式。 如果用户选择某种模式页面将切换到该模式但如果他们更改其计算机上的模式则不会影响页面。 如果用户选择模式则模式将更改为用户计算机上当前设置的 System 模式。如果用户更改其计算机上的模式则会影响页面。 第一个要求
为了满足第一个要求我们需要在从服务器提供页面之前以某种方式确定用户在其计算机上选择的模式。据我了解这是无法实现的因为服务器不知道用户在其计算机上的选择。
那么我们如何解决这个问题呢
我学到的解决此问题的技巧是在组件中呈现一个
function ThemeMonitor() {return (script dangerouslySetInnerHTML{{ __html: console.log(Theme script is running);const allCookies (document.cookie || ).split(;);const themeCookie allCookies.find((cookie) cookie.trim().startsWith(theme));if (!themeCookie navigator.cookieEnabled) {const themeDetected window.matchMedia((prefers-color-scheme: dark)).matches ? dark : light;document.cookie theme JSON.stringify({ detected: themeDetected, selected: }) ;path/;window.location.reload();},}}/
);
}然后我们可以在我们的 root.tsx 中添加
htmlheadThemeMonitor /!--more tags here...--/head!--more tags here...--/html这个技巧使我们能够在用户看到呈现的页面之前检测用户的模式。
存储在 Cookie 中的值是一个对象我将在后面进一步研究但它具有以下结构 const theme {detected: dark, selected: }第二个要求
发现如何满足这一要求是一个惊喜。说实话我不知道当用户在计算机上切换模式时可以在浏览器中检测到。
我利用首选配色方案来解决这个问题。这一创新功能允许网站无缝适应用户在其操作系统或浏览器上的首选颜色模式。通过检测用户是否选择了浅色或深色模式网站可以相应地定制其视觉外观从而提高可读性和整体浏览体验。例 media (prefers-color-scheme: dark) {/* Styles for Dark Mode */body {background-color: #1a1a1a;color: #ffffff;}
}让我们深入研究一下我如何在 ThemeMonitor 组件中实现此功能。
function ThemeMonitor() {const { revalidate } useRevalidator();useEffect(() {const themeQuery window.matchMedia((prefers-color-scheme: dark));function handleThemeChange() {const currentTheme getTheme(document.cookie);document.cookie commitTheme({...currentTheme,detected: themeQuery.matches ? dark : light,});revalidate();}themeQuery.addEventListener(change, handleThemeChange);return () {themeQuery.removeEventListener(change, handleThemeChange);};}, [revalidate]);return script dangerouslySetInnerHTML{{ __html: ***previous code here***}} /
}这里要提到的一些相关要点是
由于页面从服务器接收需要处理的主题因此在使用此钩子重新验证页面时我们有可用的更新数据
export async function loader({ request }: LoaderArgs) {const theme getTheme(request.headers.get(Cookie));return json({ theme }); // {detected: dark, selected: light}
}MatchMedia matchMedia 是一个JavaScript API通过允许您向浏览器查询特定CSS媒体查询的当前状态来实现响应式设计。它提供了一种以编程方式检测设备特征如屏幕宽度、方向和配色方案的方法。
通过为首选配色方案“深色”创建媒体查询我使用更改事件监视对此首选项的更改。每当发生更改时我都会更新 Cookie 中检测到的主题并触发重新验证。
Cookie当您使用以下格式将 Cookie 分配给文档时
document.cookie newCookie;它不会删除现有的 Cookie。相反它会设置或更新您分配的特定 Cookie。这并不直观但这就是我们拥有的 API .
第三个要求
为了满足第三个要求我采用了一种策略该策略涉及结构化数据使我能够根据需要确定检测到的主题和用户选择的主题 const theme {detected: dark, selected: ligth}这种方法使我们能够确定要在页面上应用的主题将是
const data useLoaderDatatypeof loader();
const theme data.theme.selected || data.theme.detected;如果用户选择了模式则 data.theme.selected || data.theme.detected 评估结果将是所选主题。
第四个要求
如果用户选择了以下 System 选项 该 selected 属性将保持为空。因此将应用检测到的主题。
好吧就是这样。