ARTICLE DETAIL

资讯详情

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

TanStack Router RouteApi 类详解:面向路由 ID 预绑定的类型安全 Hook 访问器与 getRouteApi 迁移指南

TanStack Router RouteApi 类详解:面向路由 ID 预绑定的类型安全 Hook 访问器与 getRouteApi 迁移指南 TanStack Router RouteApi 类详解面向路由 ID 预绑定的类型安全 Hook 访问器与 getRouteApi 迁移指南【免费下载链接】router A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router本文围绕 RouteApiClass 文档展开系统讲解 TanStack Router 中RouteApi类的定位、构造参数、实例 API 及其在tanstack/router-core中的实现细节。读完本文你将理解如何在不直接持有路由对象的文件如代码分割后的子文件中以完全类型安全的方式消费某个路由的useParams、useSearch、useLoaderData等 Hook并掌握官方推荐的getRouteApi替代写法。一、RouteApi类是什么RouteApi类提供了一组常见 Hook 的类型安全版本包括useParams、useSearch、useRouteContext、useNavigate、useLoaderData和useLoaderDeps。与直接调用这些 Hook 需要显式传入from路由 ID 不同RouteApi实例在创建时就已预绑定到一个特定的路由 ID并同时锁定该路由在已注册路由树中对应的全部类型参数类型、search 类型、loader 数据类型、context 类型等。它解决的典型场景是当路由对象无法在当前文件中直接导入时——例如代码分割文件、工具模块、跨文件复用的逻辑——你仍然可以按路由 ID 拿到完整的类型化路由 API而无需重复手写from: /invoices/$invoiceId这样的字符串字面量。注意来自原文档的重要提示该类已被弃用deprecated将在 TanStack Router 的下一个主版本中移除。官方建议使用getRouteApi函数替代二者行为完全等价详见本文 迁移指南 一节。二、构造参数构造函数选项RouteApi的构造函数只接受一个参数用于配置该RouteApi实例的options对象。opts.routeId选项类型string必填含义RouteApi实例将绑定到该路由 ID需要特别说明的是文档与源码之间的细微差异文档的选项小节将其命名为opts.routeId但官方示例与源码实现中构造参数实际写作id字段。从 React 版构造函数实现 可以确认解构出的键是id// packages/react-router/src/route.tsx简化引用 export class RouteApi TId, TRouter extends AnyRouter RegisteredRouter, extends BaseRouteApiTId, TRouter { /** * deprecated Use the getRouteApi function instead. */ constructor({ id }: { id: TId }) { super({ id }) } // ... }因此实际使用时应传入{ id: /posts }。构造返回值构造函数返回一个RouteApi实例该实例已预绑定到构造时传入的路由 ID。三、RouteApi实例上的 API结合 RouteApiType 文档与 React 版实现实例上可用的方法与组件如下均以预绑定路由 ID 为前提数据类 HookuseParams/useSearch/useRouteContext/useLoaderDeps/useLoaderData这五个 Hook 均为对应通用 HookuseParams、useSearch、useRouteContext、useLoaderDeps、useLoaderData的类型安全包装签名为useParamsTSelected TAllParams(opts?: { select?: (params: TAllParams) TSelected }): TSelected其余四个方法结构相同返回类型分别替换为TFullSearchSchema、TAllContext、TLoaderDeps、TLoaderData。opts.select可选若提供则其返回值成为 Hook 返回值并用于浅比较以决定是否触发父组件重渲染opts.structuralSharing可选boolean配置select返回值是否启用结构化共享未提供select时返回完整的数据对象若内部strict为false则返回放宽版本可能为undefined。一个值得注意的实现细节在 RouteApi 类内部useLoaderDeps与useLoaderData的包装实现都显式注入了strict: falseuseLoaderDeps: UseLoaderDepsRouteTId (opts) { return useLoaderDeps({ ...opts, from: this.id, strict: false } as any) } useLoaderData: UseLoaderDataRouteTId (opts) { return useLoaderData({ ...opts, from: this.id, strict: false } as any) }这与 routeApi.test-d.tsx 中的类型测试互相印证当以shouldThrow: false调用useParams/useSearch时返回类型会放宽为| undefined例如{ invoiceId: string } | undefined。也就是说通过RouteApi预绑定后路由未匹配这一边界情况由类型系统如实表达而非抛错。useMatchuseMatchTSelected TAllContext(opts?: { select?: (match: TAllContext) TSelected }): TSelecteduseMatch的预绑定版本返回完整RouteMatch对象或其select结果同样支持select与structuralSharing。useNavigateuseNavigate(): // navigate 函数useNavigate的预绑定版本其默认from被静态推导为当前路由的fullPath。从 实现源码 看它通过useRouter()拿到运行时 router再从router.routesById[this.id].fullPath动态解析出fromuseNavigate (): UseNavigateResult RouteTypesByIdTRouter, TId[fullPath] { const router useRouter() return useNavigate({ from: router.routesById[this.id as string].fullPath }) }类型测试 routeApi.test-d.tsx 断言了这一点对/invoices/$invoiceId路由而言navigate的静态from精确等于/invoices/$invoiceId。redirect与notFound这两个方法来自框架无关的基类BaseRouteApi// packages/router-core/src/route.ts export class BaseRouteApiTId, TRouter extends AnyRouter RegisteredRouter { id: TId constructor({ id }: { id: TId }) { this.id id } notFound (opts?: NotFoundError) { return notFound({ routeId: this.id as string, ...opts }) } redirect: RedirectFnRouteRouteTypesByIdTRouter, TId[fullPath] ( opts, ) redirect({ from: this.id as string, ...opts } as any) }redirect(opts?)redirect函数的类型安全版from自动设为路由 ID从而支持相对路径重定向返回可在beforeLoad或loader中抛出的Redirect对象notFound(opts?)等价于notFound({ routeId: this.id, ...opts })用于抛出绑定到当前路由的 404 错误。官方示例import { getRouteApi } from tanstack/react-router const routeApi getRouteApi(/dashboard/settings) export const Route createFileRoute(/dashboard/settings)({ beforeLoad: ({ context }) { if (!context.user) { // 类型安全重定向from 自动为 /dashboard/settings throw routeApi.redirect({ to: ../login, // 相对路径跳向兄弟路由 }) } }, })LinkReact 版的RouteApi还额外提供了一个预绑定的Link组件见 实现内部通过forwardRef包装Link并自动从运行时 router 中解析出该路由的fullPath作为from因此Link的目标路径校验与参数类型同样完全类型安全。四、使用示例原文档给出的示例new RouteApi写法import { RouteApi } from tanstack/react-router const routeApi new RouteApi({ id: /posts }) export function PostsPage() { const posts routeApi.useLoaderData() // ... }结合类型测试中更完整的 路由树与数据定义一个带参数、search 校验与 loader 的完整用法如下import { createRootRoute, createRoute, createRouter, getRouteApi, } from tanstack/react-router const rootRoute createRootRoute() const invoiceRoute createRoute({ getParentRoute: () rootRoute, path: /invoices/$invoiceId, validateSearch: () ({ page: 0 }), beforeLoad: () ({ beforeLoadContext: 0 }), loaderDeps: () ({ dep: 0 }), loader: () ({ data: 0 }), }) const routeTree rootRoute.addChildren([invoiceRoute]) const router createRouter({ routeTree }) // 在无法导入路由对象的文件中按 ID 获取类型化 API const invoiceApi getRouteApi(/invoices/$invoiceId) function InvoicePage() { const { invoiceId } invoiceApi.useParams() // 精确为 { invoiceId: string } const { page } invoiceApi.useSearch() // 精确为 { page: number } const { data } invoiceApi.useLoaderData() // 精确为 { data: number } return div{invoiceId} / page {page} / {data}/div }上述每个返回类型并非推断臆测而是由 routeApi.test-d.tsx 中的expectTypeOf(...).toEqualTypeOf...()断言逐一验证的useParams为{ invoiceId: string }、useRouteContext为{ beforeLoadContext: number }、useSearch为{ page: number }、useLoaderData为{ data: number }。五、源码视角预绑定是如何实现的从源码结构看React 版RouteApi的每个方法本质都是把调用者传入的选项与from: this.id合并后转发给对应的通用 Hook例如 useSearch 的包装useSearch: UseSearchRouteTId (opts) { return useSearch({ ...opts, from: this.id } as any) }类型侧的强度则来自两个泛型TId路由 ID 字面量与TRouter默认RegisteredRouter即全局已注册的路由树类型。实例方法签名中大量出现的RouteTypesByIdTRouter, TId就是从已注册路由树中按 ID 抽取该路由全部类型信息的工具类型——参数、search schema、loader 返回、fullPath 均由它推导。因此只要路由 ID 是路由树中已注册的合法字面量RouteApi上的所有调用都享受与直接写在路由对象上等价的类型检查写错 ID 会在编译期被ConstrainLiteral约束拦截见下文getRouteApi的签名。BaseRouteApi承载id、notFound、redirect位于框架无关的 router-core因此同一套预绑定语义在 React、Vue、Solid 三个适配层中保持一致Reactpackages/react-router/src/route.tsxVuepackages/vue-router/src/route.tsSolidpackages/solid-router/src/route.tsx六、从new RouteApi迁移到getRouteApi如开头弃用提示所述RouteApi类的构造函数在源码中已被标记deprecated见 route.tsx 构造函数注释。替代函数 getRouteApi 的签名与实现非常简洁——它只是RouteApi的工厂封装// packages/react-router/src/route.tsx简化引用 export function getRouteApi const TId, TRouter extends AnyRouter RegisteredRouter, (id: ConstrainLiteralTId, RouteIdsTRouter[routeTree]) { return new RouteApiTId, TRouter({ id }) }迁移只需两步将import { RouteApi }改为import { getRouteApi }将new RouteApi({ id: /posts })改为getRouteApi(/posts)。函数形式额外通过ConstrainLiteralTId, RouteIds...把路由 ID 约束为已注册路由树中的合法 ID 字面量进一步压缩了手误空间。import { getRouteApi } from tanstack/react-router const routeApi getRouteApi(/posts) export function PostsPage() { const posts routeApi.useLoaderData() // ... }七、小结RouteApi是预绑定路由 ID 已注册路由类型的类型安全 Hook 访问器适用于无法直接导入路由对象的文件构造参数为{ id }文档选项小节写作routeId以源码与官方示例的id为准返回预绑定的RouteApi实例实例提供useMatch、useRouteContext、useSearch、useParams、useLoaderDeps、useLoaderData、useNavigate以及来自BaseRouteApi的redirect、notFoundReact 版还有预绑定的LinkuseLoaderData/useLoaderDeps内部固定strict: false类型上以| undefined表达未匹配场景并有专门的类型测试保障该类已弃用官方推荐getRouteApi(routeId)行为等价且 ID 约束更强将在下个主版本移除RouteApi类建议新代码直接使用函数形式。【免费下载链接】router A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表