前言
最近我在学习 Nuxt.js 和 Nest.js 写前后端分离的项目时,当进行网络请求和 API 调用时,经常会不知道这个请求的提交参数、响应结果有什么数据字段,前端往往需要对后端返回的数据再创建一遍TS的类型,再封装对应的请求方法发送请求获取数据。
但这样为了更好的类型提示,无形之间又增加了工作量,我需要定义每个接口的 Response 与 Body 类型,就极易造成开发疲惫,不愿维护代码。
本文总共会接触到以下主要技术栈。
主要这些技术栈都与 typescript 相关,并且在 trpc 的示例应用中或多或少使用到,因此也是有必要了解一下。
Nuxt.js
安装
使用 cli 创建 Nuxt 3 应用程序
pnpm
pnpm dlx nuxi@latest init <project-name>
如果创建失败,可以手动访问下载:codeload.github.com/nuxt/starte...
安装 tRPC 库和 Zod 以进行架构和参数验证。
pnpm
pnpm add @trpc/server @trpc/client trpc-nuxt zod
配置 nuxt.config.ts
修改 nuxt.config.ts
将其转译为 ES5 包。
typescript
// nuxt.config.ts
export default defineNuxtConfig({
+ build: {
+ transpile: ['trpc-nuxt']
+ }
})
配置后端服务
1. 创建 tRPC 路由器实例
最好的是在单独的文件中创建 tRPC 实例, 并导出可复用的函数而不是整个 tRPC 实例对象。
typescript
// server/trpc/trpc.ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
export const router = t.router;
export const publicProcedure = t.procedure;
接下来,我们将初始化主路由器实例,通常称为 appRouter
。然后我们需要导出一会儿将在客户端使用的路由器类型。
typescript
// server/trpc/routers/index.ts
import { router } from './trpc';
export const appRouter = router({
// ...
});
export type AppRouter = typeof appRouter;
2. 添加查询
使用 publicProcedure.query()
向路由器添加查询。
typescript
// server/trpc/routers/index.ts
import { PrismaClient } from '@prisma/client'
import { publicProcedure, router } from './trpc';
// 实例化 `PrismaClient`
const prisma = new PrismaClient()
export const appRouter = router({
userList: publicProcedure.query(async () => {
return await prisma.user.findMany()
}),
});
export type AppRouter = typeof appRouter;
这里用到了 prisma 这个库, 还不熟悉的小伙伴可以去看看文档, 这里就不多介绍.
或者像下面这样, 简单返回个数组
typescript
// server/trpc/routers/index.ts
import { PrismaClient } from '@prisma/client'
import { publicProcedure, router } from './trpc';
// 实例化 `PrismaClient`
const prisma = new PrismaClient()
// 定义个数组
const list = [
{ id: 1, name: 'test1', email: '123@qq.com' },
{ id: 2, name: 'test2', email: '123@qq.com' },
{ id: 3, name: 'test3', email: '123@qq.com' },
]
export const appRouter = router({
userList: publicProcedure.query(async () => {
return list
}),
});
export type AppRouter = typeof appRouter;
3. 使用验证库校验客户端输入
typescript
// server/trpc/routers/index.ts
import { z } from 'zod'
import { PrismaClient } from '@prisma/client'
import { publicProcedure, router } from '../trpc'
const prisma = new PrismaClient()
const list = [
{ id: 1, name: 'test1', email: '123@qq.com' },
{ id: 2, name: 'test2', email: '123@qq.com' },
{ id: 3, name: 'test3', email: '123@qq.com' },
]
export const appRouter = router({
userList: publicProcedure.query(async () => {
return list
}),
userById: publicProcedure
.input(z.number())
.query(async (opts) => {
// input: number
const { input } = opts
return list.find(item => item.id === input)
}),
})
export type AppRouter = typeof appRouter
4. 提供 API 服务
由于 @trpc/client
没有集成 Nuxt.js
, 所以我们使用 tRPC-Nuxt 这个库.
typescript
// server/api/trpc/[trpc].ts
import { createNuxtApiHandler } from 'trpc-nuxt'
import { appRouter } from '~/server/trpc/routers'
// export API handler
export default createNuxtApiHandler({
router: appRouter
})
在客户端上调用后端服务
1. 创建 tRPC 插件,以便可以在整个应用程序中访问 tRPC Client
不熟悉的小伙伴可以查看文档 Nuxt Plugins
typescript
// plugins/trpc-client.ts
import type { AppRouter } from '~/server/trpc/routers';
import { createTRPCNuxtClient, httpBatchLink } from 'trpc-nuxt/client';
export default defineNuxtPlugin(() => {
const trpcClient = createTRPCNuxtClient<AppRouter>({
links: [
httpBatchLink({
url: '/api/trpc',
}),
],
});
return {
provide: {
trpcClient,
},
};
});
2. 最后在应用程序中测试
现在一切准备就绪,我们可以在 app.vue
中测试 api 调用
vue
// app.vue
<script setup lang="ts">
const { $trpcClient } = useNuxtApp()
// https://trpc-nuxt.vercel.app/get-started/tips/composables
// useQuery() 是 tRPC-Nuxt 框架里用 useAsyncData 包装了一层
const { data, execute, refresh } = await $trpcClient.userList.useQuery()
// @trpc/client 只有 query()
// const list = await $trpcClient.list.query()
const { data: user } = await $trpcClient.userById.useQuery(1) // 字符串类型会报错
</script>
<template>
<div>
<h1>user list</h1>
<ul>
<li v-for="item in data" :key="item.id">
{{ `name: ${item.name} email: ${item.email}` }}
</li>
</ul>
<h1>find user</h1>
<div>
{{ `name: ${user?.name} email: ${user?.email}` }}
</div>
</div>
</template>
输出
参考
Build A Full-Stack Typescript Application with Nuxt and tRPC