ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Remix 3 自定义中间件怎么写:用 context.set 提供类型化请求值

Remix 3 自定义中间件怎么写:用 context.set 提供类型化请求值 Remix 3 自定义中间件怎么写用 context.set 提供类型化请求值【免费下载链接】remixThe fully-stacked web framework项目地址: https://gitcode.com/GitHub_Trending/re/remix在 Remix 3 的应用里你经常会遇到这类需求每个请求都要带上一个请求 ID、一个数据库连接、或一个解析好的用户信息并且下游的 controller、action 希望以带类型的方式读取这些值。Remix 3 的路由器通过(context, next)中间件契约解决这个问题中间件用context.set(key, value)写入请求级值下游代码用context.get(key)读取并且 TypeScript 能根据中间件的类型推导出读取值的准确类型。本文基于 Request Handling 章节 的 Custom middleware 与 Typed request context 部分配合仓库中的真实示例走一遍从定义中间件到在 action 中类型安全地读取值的完整路径。先明确中间件能拿到什么、能做什么Remix 3 的每个中间件接收(context, next)返回一个Response或者返回next()的结果。同一次router.fetch(...)调用里所有中间件和 action 拿到的是同一个请求上下文对象它初始带有原始request、解析后的url、可变的Headers副本、生效的method、匹配到的params和当前router。这些内置字段在 Routing and Controllers 章节 的表格中有完整列出其中与本文直接相关的三个方法是context.set(key, value) // 在共享的请求上下文上存储一个请求级值 context.get(key) // 读取某个 context key 存储的值 context.has(key) // 判断某个 context key 是否已有值两条执行规则决定你写中间件时的位置await next()之前的代码在请求进入时执行之后的代码在响应回程执行可以检查或替换下游返回的响应不调用next()直接返回Response会中断链条——静态文件、CORS 预检、鉴权拒绝和缓存都靠这个机制提前应答。Remix 有三种中间件作用域router 中间件在路由匹配前对每个请求运行controller 中间件运行于该 controller 直接拥有的 actionaction 中间件只运行于单个 action。控制器和 action 中间件只在路由匹配后才执行。第一步用 createContextKey 定义一个类型化 key不要直接用字符串当 key。remix/router导出的createContextKey创建一个类型安全的 key它的泛型参数就是存储值的类型import { createContextKey, type Middleware } from remix/router; export const RequestId createContextKeystring();createContextKey也可以带一个默认值createContextKeyvalue(defaultValue)。有默认值时context.get(key)在未 set 的情况下返回默认值没有默认值时返回undefined。这一点在 RequestContext 源码 的get实现中可以直接核对。第二步写中间件并用 Middleware 类型声明它提供的值文档给出的示例是一个请求 ID 中间件在 action 执行前写入 ID在响应回程把同一个 ID 加进响应头import { createContextKey, type Middleware } from remix/router; export const RequestId createContextKeystring(); export function requestId(): Middleware{ key: typeof RequestId; value: string; } { return async (context, next) { let id crypto.randomUUID(); context.set(RequestId, id); let response await next(); let headers new Headers(response.headers); headers.set(X-Request-Id, id); return new Response(response.body, { status: response.status, statusText: response.statusText, headers, }); }; }Middleware{ key, value }这个类型参数是关键它向 Remix 的类型系统声明这个中间件往上下文里加了什么。一旦requestId()出现在有类型的 router 中间件栈里下游 controller 里context.get(RequestId)的类型就是string而不是string | undefined。如果中间件只是拒绝请求而不提供值就不需要类型参数直接返回Response且不调用next()function requireJson(): Middleware { return (context, next) { let mediaType context.headers.get(Content-Type)?.split(;, 1)[0].trim().toLowerCase(); if (mediaType ! application/json) { return new Response(Expected JSON, { status: 415 }); } return next(); }; }第三步把中间件放进 router让类型流入 controller在app/router.ts中把requestId()加进createRouter的middleware数组然后用RouterContexttypeof router从当前 router 推导出完整的应用上下文再通过模块扩展把它设为 controller 的默认上下文import { render } from remix/middleware/render; import { staticFiles } from remix/middleware/static; import { createRouter, type RouterContext } from remix/router; import { requestId } from ./middleware/request-id.ts; import controller from ./actions/controller.tsx; import { routes } from ./routes.ts; export const router createRouter({ middleware: [staticFiles(./public, { index: false }), requestId(), render()], }); export type AppContext RouterContexttypeof router; declare module remix/router { interface RouterTypes { context: AppContext; } } router.map(routes, controller);RouterContexttypeof router按中间件数组的顺序逐项收集每个中间件声明的 context 条目所以放在栈里的顺序必须满足依赖关系提供者要放在消费者前面文档给的例子是formData()在methodOverride()之前、compression()在它要包裹的staticFiles()之前。模块扩展之所以有用是因为 controller 在独立文件里创建、之后才被router.map(...)映射到这个 router它们不可能显式引用这个 router 的类型。文档同时提醒一个应用里有多个 router 时应改为给每个 controller 传显式的 context 类型而不是设一个全局默认。到此controller 里的 action 就能直接以string类型读取请求 ID// inside an action: action(context) { let id context.get(RequestId); // 类型是 string // ... }可选把值安装为 context 的直接属性context.set(key, value)接受第三个参数{ property }用于把值安装成请求上下文上的一个只读直接属性。仓库里的真实例子是 bookstore 演示的数据库中间件import { createContextKey, type Middleware } from remix/router export const databaseContext createContextKeyDatabase() export function loadDatabase(): Middleware{ key: typeof databaseContext value: Database property: db } { return (context, next) { context.set(databaseContext, db, { property: db }) return next() } }安装后下游可以像context.db一样直接访问而不是每次context.get(databaseContext)。内置的formData()提供context.formData、render()提供context.render(...)走的就是同一机制。从 RequestContext 实现 可以看到这个机制的三条约束违反时会在运行时抛错属性名不能与RequestContext上已有的属性如request、url、headers重名同一个 context key 不能先后用两个不同的属性名不同的 context key 不能安装到同一个属性名。验证类型层面和响应头两个检查点类型检查。判断中间件是否正确接入的最直接方式是编译requestId()进入createRouter的middleware数组后context.get(RequestId)推导为string如果中间件不在栈里同一次读取推导为string | undefinedkey 无默认值时运行结果是undefined。用tsc或你 IDE 的类型提示确认这一点不需要运行服务器。响应头检查。按上面的requestId()示例每个经过该中间件的响应都会带上X-Request-Id请求头。启动server.ts默认端口见 Request Handling 章节 的 Node 入口示例默认 44100后对任意路由发起请求检查响应头中是否存在X-Request-Id。它由crypto.randomUUID()生成每次请求值不同文档没有给出固定预期值检查时应以该请求的响应头里存在且与后续日志一致为判断依据。边界与替代路径中间件链存变量时。内联middleware: [...]数组是默认写法。只有当一条可复用的中间件链必须存进变量时才用createMiddleware(...)保留它的 tuple 类型并用MiddlewareContexttypeof middleware推导结果上下文。action 之外的辅助代码要读值时。请求上下文是显式的 per-request 值不是全局。中间件和 action 之外的 helper 要拿它从remix/middleware/async-context引入asyncContext()加进中间件栈然后在 helper 里调getContext()该中间件依赖 Node 的 async context 支持跨运行时可移植的做法是把需要的值作为函数参数传进去。职责边界。文档建议自定义中间件聚焦单一请求生命周期关注点路由特定的数据加载或校验放 action跨多路由共享的行为——请求 ID、session、认证、安全头、数据库访问——才放进中间件管线。运行时可移植性。router 契约本身可移植但中间件栈里每个包未必例如静态文件和压缩中间件用了 Node 的 API在 worker 上应改用平台能力。写完后对照两条文档给出的选型标准自查一遍这个值是否真正请求级换掉它应该随请求变化下游读取点是否需要类型保证而不是每次判空两者都成立时createContextKeycontext.setMiddleware{ key, value }就是完整方案。【免费下载链接】remixThe fully-stacked web framework项目地址: https://gitcode.com/GitHub_Trending/re/remix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表