发布于 2026-01-06 0 阅读
0

Vue3 + TS + Vue Query + Express + tRPC:设置示例

Vue3 + TS + Vue Query + Express + tRPC:设置示例

目录

介绍

最近我在谷歌上搜索有关 Web 开发趋势的信息,以便了解最新的工具/库/框架,然后偶然发现了 tRPC。

tRPC 代表TypeScript 远程过程调用,正如其官网所述,它的目的是轻松实现端到端的类型安全 API。本质上,它允许您使用 TypeScript 的所有强大功能,公开可从客户端(前端)调用的服务器函数。

tRPC官方网站包含丰富的示例文档

tRPC 是确保客户端和服务器之间(通过 API 调用)正确通信的另一种方式。您可能已经考虑使用 GraphQL 来实现这一点,但使用 tRPC 无需学习新的语言,它本身也不是一个模式。而 GraphQL 既是一个模式,也是一种语言,它用于详细描述您可以从服务器调用的函数的“结构”。

实验:何不尝试使用最新版本的VueViteTypeScript,并尝试集成tRPC,看看效果如何?
我搜索了一些基于 Vue 并使用 tRPC 的项目,但结果绝大多数都是基于 React/Next.js 的……所以我决定先从一个基于 React 的项目开始,然后再进行其他实验。


- 我会在文章中提供所有相关资源的链接
。- 这只是一个实验性的想法,旨在集成几个现代软件包并创建一个非常简单的项目
。- 本文更适合已有一定 Web 开发经验的读者,但我会尽量提供一些额外的解释。

设置

首先,我观看了Jack Herrington关于“ tRPC:智能简易 API ”的精彩视频,并按照他的步骤操作,然后想知道使用 Vue 3 和Vue Query分别代替 React 和 React Query会有多难。

下一节展示了根据 Jack 的步骤并修改为使用 Vue 后最终的文件夹结构。

项目文件夹结构

文件夹结构

这是一个使用 Yarn 工作区的单体仓库。
服务器项目位于api-server文件夹中,前端项目位于client文件夹中。

服务器和客户端都是在根目录下启动的yarn start,正如您在根文件夹下的 package.json 文件中看到的那样:
"start": "concurrently \"wsrun --parallel start\""

服务器脚本

这是服务器代码,我们在这里创建了我们的 express 应用程序,并告诉它使用 cors(允许从端口 3000 到 8080 的调用),还使用 ​​trpcExpress 中间件并注册路由器。



// packages\api-server\index.ts
import express from 'express';
import * as trpcExpress from '@trpc/server/adapters/express';
import { appRouter } from './router/app';
import cors from 'cors';

const main = async () => {
  const app = express();
  app.use(cors());
  const port = 8080;

  app.use(
    '/trpc',
    trpcExpress.createExpressMiddleware({
      router: appRouter,
      createContext: () => null,
    })
  );

  app.listen(port, () => {
    console.log(`api-server listening at http://localhost:${port}`);
  });
};

main();


Enter fullscreen mode Exit fullscreen mode

路由器

以下代码显示了包含接入点的路由器:

  • 2 个查询端点(类似于 REST GET 端点):
    • 问候
    • 获取消息
  • 1 个突变端点(类似于 REST POST 端点):
    • 添加消息

注意:除了添加数据外,变异还可以更新或删除数据。

您还可以看到我正在使用zod,这是一个“TypeScript 优先的模式声明和验证库”。

该软件包将用于验证我的查询/变更输入(如有必要,这些验证甚至可以抛出验证消息)。



z.string().uuid({ message: "Invalid UUID" });


Enter fullscreen mode Exit fullscreen mode

注意:您还可以使用 zod 从 zod 对象推断类型,将它们存储为类型并在任何地方重用它们



// packages\api-server\router\app.ts
import * as trpc from '@trpc/server';
import { z } from 'zod';
import { v4 as uuidv4 } from 'uuid';

export interface ChatMessage {
  id: string;
  user: string;
  message: string;
}

const messages: ChatMessage[] = [
  { id: uuidv4(), user: 'User1', message: 'This is my the first message!' },
  { id: uuidv4(), user: 'User2', message: 'Hello there 🎉' },
];

export const appRouter = trpc
  .router()
  .query('greetings', {
    resolve() {
      return {
        message: 'Greetings from /trpc/greetings:)',
      };
    },
  })
  .query('getMessages', {
    input: z.number().default(10),
    resolve({ input }) {
      return messages.slice(-input);
    },
  })
  .mutation('addMessage', {
    input: z.object({
      user: z.string(),
      message: z.string(),
    }),
    resolve({ input }) {
      const newMessage: ChatMessage = {
        id: uuidv4(),
        ...input,
      };
      messages.push(newMessage);
      return input;
    },
  });

export type AppRouter = typeof appRouter;


Enter fullscreen mode Exit fullscreen mode

在这种情况下,消息只会存储在内存中,因为我没有使用数据库。(这样演示起来也更快)。
还可以创建不同的路由器,每个路由器包含不同的查询/变更操作,然后合并这些路由器,以便在客户端轻松地从某个路由器访问特定的查询。

Vue 查询初始化

这是通过 VueQueryPlugin 在 main.ts 文件中初始化 vue-query 的方法,然后 Vue 应用程序实例会使用它:



// packages\client\src\main.ts
import { createApp } from 'vue';
import { VueQueryPlugin } from 'vue-query';
import './style.css';
import App from './App.vue';

createApp(App).use(VueQueryPlugin).mount('#app');


Enter fullscreen mode Exit fullscreen mode

你可能会问,为什么一开始要使用 Vue Query 呢?
“我完全可以用 fetch/axios 来完成所有的 API 调用,对吧?”

没错,不过这个软件包确实提供了一些开箱即用的实用功能,例如缓存、重试、重新获取、无限查询(用于无限滚动)等等。以下是随着项目复杂性增加,可能会出现的一些挑战(摘自官方文档):

  • 缓存……(可能是编程中最难的事情之一)
  • 将对同一数据的多个请求去重,合并成单个请求。
  • 在后台更新“过期”数据
  • 了解数据何时“过时”
  • 尽快反映数据更新
  • 性能优化,例如分页和延迟加载数据
  • 管理内存和服务器状态的垃圾回收
  • 使用结构共享记忆查询结果

这些钩子提供了一组标准属性/函数,供您在应用程序中使用。例如,useQuery 钩子:注意:您需要访问的数据位于名为data 的属性中。
使用查询示例

tRPC 客户端

这里我们定义了 tRPC 客户端调用中需要使用的 URL,以及可以使用的类型(来自 AppRouter)。(稍后我们将在 App.vue 组件中导入这个 trpc 常量):



// packages\client\src\api\trpc.ts
import { createTRPCClient } from '@trpc/client';
import { AppRouter } from 'api-server/router/app';

export const trpc = createTRPCClient<AppRouter>({
url: 'http://localhost:8080/trpc',
});

Enter fullscreen mode Exit fullscreen mode




应用程序组件

为了简单起见,我决定在这个组件中执行 tRPC 客户端调用。
:我使用的是 Vue 的脚本配置,目前为止使用体验很棒 :)



<template>
<div class="trpc-example">
<h1>Vue 3 + vue-query + tRPC example</h1>
<Error
v-if="getMessagesHasError"
error-message="Something went wrong - cannot fetch data"
cta-text="Refetch data"
@click="refetch()"
/>
<Error
v-if="addMessageHasError"
error-message="Something went wrong - cannot submit message"
cta-text="Reset error"
@click="reset"
/>
<div v-if="showFormAndMessages" class="trpc-example__container">
<SendMessageForm :form="form" @submit-form="handleSubmitForm" />
<h2 v-if="isLoading">Data is being loaded</h2>
<Message v-for="chatMessage in data" :key="chatMessage.id" :chat-message="chatMessage" />
</div>
</div>
</template>

<script setup lang="ts">
import { computed, reactive } from 'vue';
import Message from './components/Message.vue';
import SendMessageForm from './components/SendMessageForm.vue';
import Error from './components/Error.vue';
import { useQuery, useMutation, useQueryClient } from 'vue-query';
import { trpc } from './api/trpc';
import { Form } from '../types';

const queryClient = useQueryClient();

const form = reactive({
user: '',
message: '',
});

const getMessages = () => trpc.query('getMessages');
const {
isError: getMessagesHasError,
isLoading,
data,
refetch,
} = useQuery('getMessages', getMessages, {
refetchOnWindowFocus: false,
});

const addMessage = (form: Form) => trpc.mutation('addMessage', form);
const { error: addMessageHasError, mutate, reset } = useMutation('addMessage', addMessage);

const handleSubmitForm = () => {
mutate(form, {
onSuccess: () => {
queryClient.invalidateQueries('getMessages');
},
});
};

const showFormAndMessages = computed(() => {
return !getMessagesHasError.value && !addMessageHasError.value;
});
</script>

Enter fullscreen mode Exit fullscreen mode




应用及示例

显然,与此项目互动最好的方式是在本地运行它,看看你能用它做什么。以下是一些示例:

这就是客户端的样子(是的,我知道,用户界面看起来很棒!)。Vue.js 开发者工具还会显示查询信息:
应用用户界面和开发者工具

来自 /trpc/greetings 的数据:
问候示例数据

来自 /trpc/getMessages 的数据:
getMessages 示例数据

以下示例展示了如何更改服务器端功能并在客户端观察 TS 安全检查:
Ts 安全 1Ts 安全 2

您也可以从客户端重命名服务器函数(由于某些原因,我无法从服务器端重命名符号):
Ts安全

阻止查询请求,然后调用重新获取函数及其重试机制的示例:
重新获取

以下示例演示了如何阻止变更请求,然后调用重置函数。这将重置错误状态:
重置错误

更多实用链接

我可能会创建另一个仓库,探索一个更实际的项目,使用 Nuxt、tRPC 和 Vue Query,并连接数据库和使用 ORM Prisma,类似于 Alex 在这个非常棒的入门仓库中所做的:https://github.com/trpc/examples-next-prisma-starter

希望这篇文章对您有所帮助,也希望它能让您今天有所收获 :)

文章来源:https://dev.to/alousilva/vue3-typescript-express-trpc-setup-example-2mlh