ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

在 TanStack Router 中集成 tRPC 与 React Query:全栈类型安全实战指南

在 TanStack Router 中集成 tRPC 与 React Query:全栈类型安全实战指南 在 TanStack Router 中集成 tRPC 与 React Query全栈类型安全实战指南【免费下载链接】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本文以仓库中的examples/react/with-trpc-react-query示例为蓝本讲解如何将 tRPC 与 TanStack Query 无缝集成进 TanStack Router 的 React 应用覆盖服务端 tRPC Router 定义、客户端 Options Proxy 封装、路由 loader 中的数据预取、URL 状态同步以及前后端双构建的生产部署流程。读完本文你将掌握一套端到端类型安全、自带数据缓存与乐观更新能力的前后端一体式全栈架构方案。示例概览与核心价值tRPC 的核心卖点是端到端类型安全API 的类型定义从服务端自动流动到客户端不再需要手写 API 客户端、手动维护类型或运行时校验层。而 TanStack Query 负责服务端状态的管理——缓存、重试、失效与乐观更新。TanStack Router 则在前端提供类型安全的路由、路径参数与搜索参数解析。三者结合后路由的loader可以直接在服务端数据返回前预取并填充 Query 缓存页面组件再通过useQuery消费同一份数据避免重复请求与闪烁加载。示例 README.md 将其总结为五点能力tRPC 与 TanStack Router 的深度集成使用 TanStack Query 进行数据获取带缓存的类型安全 API 调用端到端类型安全乐观更新Optimistic updates能力支撑示例的应用是一个带/dashboard/posts列表与/dashboard/posts/$postId详情页的小型博客后台配套一个内存中的文章数据源完整演示了从服务端过程procedure定义到前端消费的整条链路。快速开始基于示例创建新项目示例提供了基于gitpick的脚手架命令可以一键把整个示例目录复制为你的新项目npx gitpick TanStack/router/tree/main/examples/react/with-trpc-react-query with-trpc-react-query说明该命令从 TanStack Router 仓库中摘取本示例目录到当前文件夹新目录名为with-trpc-react-query。进入项目后安装依赖并启动开发服务器pnpm install pnpm dev示例的dev脚本在 package.json 中定义为pnpm tsx ./src/server/server.ts --watch即通过tsx以 watch 模式直接运行 Express 服务器脚本Vite 以 middleware 模式挂载在同一个 Express 实例上因此开发时前端页面与/trpc接口同源客户端无需显式配置完整 URL。生产构建分两步脚本见 package.jsonpnpm build # 等价于 pnpm run build:server pnpm run build:client pnpm start # NODE_ENVproduction node dist/server/server.jsbuild:servervite build --mode server产出dist/serverSSR 构建build:clientvite build tsc --noEmit产出dist/client并执行 TypeScript 类型检查。vite.config.ts通过configEnv.mode区分两种构建客户端构建输出到dist/client并拷贝public资源服务端构建以 src/server/server.ts 为入口做 SSR 打包rolldownOptions.input指定入口。两种模式共用同一套插件链tailwindcss()、tanstackRouter({ target: react, autoCodeSplitting: true })与react()其中autoCodeSplitting: true会按路由自动做代码分割。服务端用 Express 挂载 tRPC RoutertRPC 服务端定义集中在 src/server/trpc.ts这是全栈类型安全的类型源头import { initTRPC } from trpc/server import { createExpressMiddleware } from trpc/server/adapters/express import type { CreateExpressContextOptions } from trpc/server/adapters/express const createTRPContext ({ req, res }: CreateExpressContextOptions) ({}) type TRPCContext AwaitedReturnTypetypeof createTRPContext const t initTRPC.contextTRPCContext().create() const POSTS [ /* 10 条模拟文章数据 */ ] export const appRouter t.router({ hello: t.procedure.query(() Hello world!), posts: t.procedure.query(async (_) { await new Promise((resolve) setTimeout(resolve, 1000)) return POSTS }), post: t.procedure.input(String).query(async (req) { await new Promise((resolve) setTimeout(resolve, 500)) return POSTS.find((p) p.id req.input) }), }) export const trpcMiddleWare createExpressMiddleware({ router: appRouter, createContext: createTRPContext, }) export type AppRouter typeof appRouter几个关键点initTRPC.contextTRPCContext().create()定义了请求上下文类型。本示例上下文为空对象实际项目可在其中注入req/res、数据库连接、鉴权用户等三个 procedure 演示了 tRPC 的两种形态无参查询hello、posts与带输入校验的查询postinput(String)声明入参为字符串。posts与post人为加入了 1 秒与 0.5 秒延迟用于直观观察 loader 预取与 Spinner 的表现export type AppRouter typeof appRouter将路由类型导出供客户端以纯类型方式引用——这是端到端类型安全的枢纽createExpressMiddleware把 tRPC Router 暴露为标准 Express 中间件。服务器装配在 src/server/server.ts 中app.use(/trpc, trpcMiddleWare)把 tRPC 端点挂在/trpc路径下随后根据环境分支开发模式通过vite.createServer({ middlewareMode: true, appType: custom, hmr: { port: HMR_PORT } })创建 Vite 中间件挂载到 Express并用app.get(/{*splat})兜底返回经transformIndexHtml处理的index.html从而让前端路由回退到 SPA生产模式express.static托管dist/client静态资源同样的/{*splat}兜底返回index.html支持前端路由端口默认3000process.env.PORT可覆盖HMR 端口默认3001。客户端TRPC Options Proxy 与 Router 上下文客户端集成的枢纽是 src/router.tsx。它利用trpc/tanstack-react-query提供的createTRPCOptionsProxy把 tRPC 过程直接转化为 React Query 的 options 对象import { createRouter as createTanStackRouter } from tanstack/react-router import { QueryClient, QueryClientProvider } from tanstack/react-query import { createTRPCClient, httpBatchLink } from trpc/client import { createTRPCOptionsProxy } from trpc/tanstack-react-query import { routeTree } from ./routeTree.gen import type { AppRouter } from ./server/trpc export const queryClient new QueryClient() export const trpc createTRPCOptionsProxyAppRouter({ client: createTRPCClient({ links: [ httpBatchLink({ url: /trpc, }), ], }), queryClient, }) export function createRouter() { const router createTanStackRouter({ routeTree, scrollRestoration: true, defaultPreload: intent, context: { trpc, queryClient, }, defaultPendingComponent: () ( div className{p-2 text-2xl} Spinner / /div ), Wrap: function WrapComponent({ children }) { return ( QueryClientProvider client{queryClient} {children} /QueryClientProvider ) }, }) return router } declare module tanstack/react-router { interface Register { router: ReturnTypetypeof createRouter } }要点拆解createTRPCClient通过httpBatchLink指向/trpc。由于开发模式 Vite 与 Express 同源直接写相对路径/trpc即可无需拼主机名createTRPCOptionsProxyAppRouter接收客户端与queryClient之后trpc.posts.queryOptions()、trpc.post.queryOptions(postId)返回的即是可直接传给useQuery/ensureQueryData的标准 React Query options同时保留完整的入参与出参类型推导context中注入trpc与queryClient使路由的loader能通过context访问它们实现“进组件前先填缓存”Wrap用QueryClientProvider包裹整个应用让所有路由组件共享同一个 QueryClient底部declare module注册 Router 实例类型配合routeTree.gen.ts实现Link、useNavigate、useParams等 API 的全面类型推导。入口 src/main.tsx 通过createRouter()创建 Router 实例并以RouterProvider挂载到#root仅在 root 为空时渲染避免 HMR 重复挂载。路由 loader 中的数据预取进入页面前先填缓存示例最精彩的部分是路由 loader 与 tRPC 查询 options 的配合——数据在页面渲染前就已进入 Query 缓存页面组件直接命中缓存几乎无加载闪烁。文章列表路由 src/routes/dashboard.posts.tsxexport const Route createFileRoute(/dashboard/posts)({ errorComponent: () Oh crap!, loader: async ({ context: { trpc, queryClient } }) { await queryClient.ensureQueryData(trpc.posts.queryOptions()) return }, pendingComponent: Spinner, component: DashboardPostsComponent, }) function DashboardPostsComponent() { const postsQuery useQuery(trpc.posts.queryOptions()) const posts postsQuery.data || [] return ( // 遍历 posts用 Link to/dashboard/posts/$postId params{{ postId }} preloadintent // 渲染文章列表右侧 Outlet / 渲染子路由 ) }loader中queryClient.ensureQueryData(trpc.posts.queryOptions())若缓存未命中则发起请求并填充缓存loader 完成后组件挂载时useQuery(trpc.posts.queryOptions())直接读到已缓存数据pendingComponent: Spinner在 loader 执行期间显示加载态errorComponent: () Oh crap!提供最小的错误兜底列表中每个Link使用preloadintent配合 Router 的defaultPreload: intent用户鼠标悬停/聚焦时即触发预取进一步提升跳转响应速度MatchRoute结合pending属性在目标路由进入 pending 状态时展示 Spinner。文章详情路由 src/routes/dashboard.posts.$postId.tsx 演示了带参数的预取export const Route createFileRoute(/dashboard/posts/$postId)({ validateSearch: z.object({ showNotes: z.boolean().optional(), notes: z.string().optional(), }), loader: async ({ context: { trpc, queryClient }, params: { postId } }) { await queryClient.ensureQueryData(trpc.post.queryOptions(postId)) }, pendingComponent: Spinner, component: DashboardPostsPostIdComponent, }) function DashboardPostsPostIdComponent() { const postId Route.useParams({ select: (d) d.postId }) const postQuery useQuery(trpc.post.queryOptions(postId)) const post postQuery.data // ... }loader 从params拿到postId调用trpc.post.queryOptions(postId)预取单篇文章入参类型由 tRPC 服务端input(String)约束天然类型安全Route.useParams({ select: ... })精确订阅postId参数避免不必要的重渲染useQuery(trpc.post.queryOptions(postId))消费缓存服务端返回undefined找不到文章时组件渲染 “Post not found”。URL 即状态搜索参数与笔记同步详情页还演示了 TanStack Router 的搜索参数search params能力配合 tRPC 数据展示“Notes 存进 URL”的交互模式const search Route.useSearch() const navigate Route.useNavigate() const [notes, setNotes] React.useState(search.notes ?? ) React.useEffect(() { navigate({ search: (old) ({ ...old, notes: notes ? notes : undefined }), replace: true, params: true, }) }, [notes])validateSearch用 zod 声明搜索参数结构showNotes?: boolean与notes?: string非法输入会被过滤保证useSearch返回值类型安全用户输入笔记时navigate将内容写入 URL 的 search 部分replace: true不污染历史记录因此复制 URL 到新标签页即可恢复笔记内容——这正是示例中 “Notes are stored in the URL” 的交互逻辑Link的search函数式更新用于切换showNotes显隐params: true保留当前路径参数。生产运行与构建细节生产模式下pnpm start以NODE_ENVproduction启动 dist/server/server.js该构建产物由vite build --mode server生成。服务器此时不再加载 Vite 中间件而是express.static(path.resolve(__dirname, ../client))托管静态资源/trpc中间件继续提供 APIapp.get(/{*splat})对未知路径回退到index.html保证前端路由如直接访问/dashboard/posts/3在刷新后仍能正确渲染。整体请求流可概括为浏览器请求页面 → Express 命中 SPA 兜底返回 HTML → 路由加载执行 loader →ensureQueryData通过/trpc批量请求数据填充 Query 缓存 → 组件渲染并复用缓存。得益于httpBatchLink同一时间片的多个查询会自动合并为一次 HTTP 请求减少网络往返。扩展阅读本示例配套的with-trpc示例无 React Query 版本位于 examples/react/with-trpc可对照理解 Options Proxy 模式带来的缓存与预取收益若你的应用需要把服务端状态在 SSR 阶段同步给客户端可参考 packages/react-router-ssr-query 与 packages/router-ssr-query-core 的源码实现路由文件路由的自动生成与代码分割由 packages/router-plugin 与vite.config.ts中的tanstackRouter({ autoCodeSplitting: true })驱动想了解 Router 上下文注入与 loader 执行时机可阅读 packages/router-core/src 下路由匹配与 loader 相关实现如route.ts、router.ts。掌握这套tRPC React Query TanStack Router组合后你将获得一条从数据库到 UI 全程类型安全的开发链路服务端定义appRouter客户端用createTRPCOptionsProxy零成本获得类型化查询函数路由 loader 负责预取填缓存组件只管消费缓存并保持 URL 状态同步。【免费下载链接】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),仅供参考
返回列表