在Next.js中集成和使用tRPC

一、TRPC 简介

TRPC(Tiny Remote Procedure Call) 是一个专为全栈 TypeScript 开发人员设计的库,它允许你在前端和后端之间轻松地创建类型安全的 API 端点。由于 API 合约的类型错误会在构建时被捕获,因此可以降低应用程序在运行时出错的可能性。你可以在TRPC官方文档中找到更多关于如何配置和使用 TRPC 的信息。

TRPC官网:trpc.io/docs/client...

二、安装依赖

首先,我们需要安装 TRPC 和其他相关依赖

bash 复制代码
npm install @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod

三、创建 TRPC 客户端

我们需要创建一个 TRPC 客户端,该客户端将用于在前端发起请求

ts 复制代码
// src/app/_trpc/client.ts
import { AppRouter } from '@/trpc'
import { createTRPCReact } from '@trpc/react-query'

export const trpc = createTRPCReact<AppRouter>({})

这里需要的 AppRouter 类型将在后面创建。

四、创建 TRPC 路由

首先初始化一个tRPC服务器,并导出其路由器和过程函数以供后续使用。

ts 复制代码
// src/trpc/trpc.ts
import { initTRPC } from '@trpc/server'
const t = initTRPC.create()
export const router = t.router
export const publicProcedure = t.procedure

参考:

trpc.io/docs/server...

trpc.io/docs/server...

接下来,我们需要创建 TRPC 路由。

ts 复制代码
//src/trpc/index.ts
import { publicProcedure, router } from './trpc'

export const appRouter = router({
  test: publicProcedure.query(() => {
    return String(Math.random())
  }),
  //   ...
})

// export type definition of API
export type AppRouter = typeof appRouter

以上路由中定义了一个名为test的API方法,使用query用于查询,这里返回一个随机字符串,并且导出了相关的类型以确保类型安全。

五、创建 TRPC 提供者

接着,我们需要创建一个 TRPC 提供者,它将包裹我们的应用,并提供 TRPC 客户端和查询客户端。

ts 复制代码
//src/components/Providers.tsx
'use client'
import { trpc } from '@/app/_trpc/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { httpBatchLink } from '@trpc/client'
import { PropsWithChildren, useState } from 'react'
import { absoluteUrl } from '@/lib/utils'

const Providers = ({ children }: PropsWithChildren) => {
  const [queryClient] = useState(() => new QueryClient())
  const [trpcClient] = useState(() =>
    trpc.createClient({
      links: [
        httpBatchLink({
          url: absoluteUrl('/api/trpc'),
        }),
      ],
    })
  )

  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    </trpc.Provider>
  )
}

export default Providers

然后,创建处理路由的工具函数

ts 复制代码
// src/lib/utils.ts
export function absoluteUrl(path: string) {
  if (typeof window !== 'undefined') return path
  if (process.env.PRO_URL) return `${process.env.PRO_URL}${path}`
  return `http://localhost:${process.env.PORT ?? 3000}${path}`
}

创建配置文件

  • .env.local
  • env.example
  • .env
  • .env.production

exampleproduction添加以下内容,PRO_URL仅需要在生产环境配置域名即可:

ini 复制代码
# 生产环境必填
PRO_URL="http://abc.com"
# 端口
PORT="3000"

同时需要将.env.env.production添加到git忽略文件

在你的布局组件中使用 Providers 组件:

ts 复制代码
// src/app/layout.tsx
import Providers from '@/components/Providers'

 <Providers>{children}</Providers>

六、配置 TRPC 服务端

最后,我们需要在服务端托管 TRPC

ts 复制代码
// src/app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import { appRouter } from '@/trpc'

const handler = (req: Request) =>
  fetchRequestHandler({
    endpoint: '/api/trpc',
    req,
    router: appRouter,
    createContext: () => ({}),
  })

export { handler as GET, handler as POST }

七、在客户端发起 API 请求

现在,你可以在客户端发起 API 请求。例如,创建一个名为 DashboardPage 的新页面,并添加以下代码:

ts 复制代码
// src/app/dashboard/page.tsx
'use client'
import { trpc } from '@/app/_trpc/client'

const DashboardPage = () => {
  const { data } = trpc.test.useQuery()
  return (
    <div>
      <p>Welcome to the dashboard!</p>
      <p>{data}</p>
    </div>
  )
}

export default DashboardPage

现在,你应该可以在页面看到打印出的字符串。同时,你也可以看到 data 的类型提示为 string | undefined

在 TRPC 路由中,你可以直接操作数据库,进行各种 CRUD 操作。

八、重试策略

参考:

useQuery:trpc.io/docs/client...

useUtils:trpc.io/docs/client...

下面给出示例,如加载中,错误重试,重试

修改一下test API,让其有错误的可能

ts 复制代码
// src/trpc/index.ts
import { publicProcedure, router } from './trpc'
import { TRPCError } from '@trpc/server'
export const appRouter = router({
  test: publicProcedure.query(() => {
    const randomNumber = Math.random()

    if (randomNumber <= 0.7) {
      throw new TRPCError({
        code: 'INTERNAL_SERVER_ERROR',
        message: 'Something went wrong',
      })
    } else {
      return String(randomNumber)
    }
  }),
  //   ...
})

// export type definition of API
export type AppRouter = typeof appRouter

客户端请求

ts 复制代码
// src/app/dashboard/page.tsx
'use client'
import { trpc } from '@/app/_trpc/client'
import { Button } from '@/components/ui/button'

const DashboardPage = () => {
  const { data, isLoading } = trpc.test.useQuery()
  const utils = trpc.useUtils()
  return (
    <div>
      <p>Welcome to the dashboard!</p>
      <p>{data}</p>
      <Button onClick={() => utils.test.invalidate()}>
        {isLoading ? 'Loading...' : 'Click me'}
      </Button>
    </div>
  )
}

export default DashboardPage

默认失败了4次就停止访问了,这是为什么?

trpc.getUserInfo.useQuery() 是一个 React Query 的 hook。React Query 有一个默认的错误重试策略,当请求失败时,它会自动尝试重新请求。默认情况下,React Query 会尝试重新请求3次,如果所有的重试都失败,那么它会停止重试,并将错误返回给用户。

这种自动重试的策略对于处理临时的网络问题或者服务器问题是非常有用的。例如,如果服务器暂时无法响应,那么通过自动重试,我们可能可以在服务器恢复正常后获取到数据,而无需让用户手动刷新页面。然而,对于永久性的错误(例如,用户没有进行身份验证,因此收到了401错误),这种自动重试的策略可能不是非常有用,因为重试不太可能改变结果。

如果你不想让 React Query 在请求失败时自动重试,你可以在调用 useQuery() 方法时传入一个配置对象,简单的设置 retry 属性为 0或者false。例如:

ts 复制代码
const { data, isLoading } = trpc.test.useQuery(undefined, { retry: 0 })

这样,当请求失败时,React Query 就不会尝试重新请求。

retry 还可以使用函数来实现个性化的策略,如我们希望这种客户端的错误重试1次就可以了,服务端的错误可以多试几次

typescript 复制代码
// src/lib/utils.ts
import { TRPCClientError } from '@trpc/client'

const MAX_QUERY_RETRIES = 3
const SKIPPED_HTTP_CODES = [401, 402, 403, 404]

export const reactQueryRetry = (failureCount: number, error: unknown) => {
  if (
    error instanceof TRPCClientError &&
    SKIPPED_HTTP_CODES.includes(error.shape?.data?.httpStatus ?? 0)
  ) {
    return failureCount < 1
  }
  return failureCount < MAX_QUERY_RETRIES
}

使用方法如下

ts 复制代码
import { reactQueryRetry } from '@/lib/utils'

const { data, isLoading } = trpc.test.useQuery(undefined, {
    retry: reactQueryRetry,
})

在使用重试时,isLoading并不会触发,需要使用isFetching,再次优化

ts 复制代码
const { data, isLoading, isFetching } = trpc.test.useQuery(undefined, {
    retry: reactQueryRetry,
})
<Button onClick={() => utils.test.invalidate()}>
    {isFetching || isLoading ? 'Loading...' : 'Click me'}
</Button>

现在,我们基本可以使用TRPC进行开发了,后面重点会讲到中间件的使用。

相关推荐
轻口味42 分钟前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami1 小时前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
吃杠碰小鸡1 小时前
lodash常用函数
前端·javascript
emoji1111111 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼2 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs
m0_748250032 小时前
Web 第一次作业 初探html 使用VSCode工具开发
前端·html
一个处女座的程序猿O(∩_∩)O2 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
m0_748235952 小时前
web复习(三)
前端
AiFlutter2 小时前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter
麦兜*2 小时前
轮播图带详情插件、uniApp插件
前端·javascript·uni-app·vue