使用trpc和使用next.js自带route,有什么区别

🚀 1. 开发体验

对比项 tRPC Next.js API Routes
代码量 少,前后端共用逻辑和类型 需要编写前后端的类型和接口
前端调用方式 trpc.add.useQuery() 直接调用 fetch('/api/add') 手动调用
输入参数校验 使用 zod 直接在路由中校验 需手动编写 if...else 逻辑
类型提示 前后端类型一致,自动推导 需要手动定义和维护前后端类型
错误处理 内置 errorLink 和自动捕获 需手动在try-catch中处理错误

解释:

  • tRPC 中,前端的输入和后端的接口是完全类型对齐的,前端可以自动感知后端的参数和返回值类型。
  • Next.js API 需要手动维护前端调用和后端接口的类型,容易出错。

📦 2. 类型安全 (Type-Safe)

对比项 tRPC Next.js API Routes
类型自动生成 ✅ 全程类型安全,前后端类型共享 ❌ 需要手动编写前后端类型声明
类型检测 ✅ 传错类型,开发时就会报错 ❌ 类型错误会在运行时才发现
TypeScript 友好 ✅ 类型从输入到输出自动联动 ❌ 前端和后端类型不连贯

解释:

  • tRPC 可以确保端到端的类型安全 ,前端调用的 trpc.add.useQuery({ a: 1, b: 2 }) 会自动检查类型是否正确。
  • Next.js API Routes 只能在接口中用 zodYup 等库手动验证参数,但前端调用 fetch('/api/add') 不会有类型提示。

⚡️ 3. 性能和性能优化

对比项 tRPC Next.js API Routes
性能 比 Next.js API 更快 (无 JSON 解析) 依赖 fetch() 解析和序列化 JSON
请求开销 直接在前后端共享对象和数据流 使用 HTTP 请求,数据需要序列化
数据传输 不需要 JSON 序列化 (内部优化) 需手动 JSON 序列化/反序列化
服务端渲染 (SSR) 支持 SSG 和 SSR 需要手动调用 getServerSideProps
缓存和预取 内置 React Query 支持的缓存 需要自己实现 useSWR

解释:

  • tRPC 使用React Query 做数据请求,支持缓存、重试、请求状态等功能。
  • Next.js API 必须依赖 fetch,每次调用都得序列化/反序列化 JSON 数据,多了一步开销

总结:

  • 如果你想要一个高性能的调用,tRPC 是更好的选择,因为它不需要请求序列化 JSON 数据
  • Next.js API Routes 的请求更接近 REST API,性能略逊于 tRPC。

🛠️ 4. 维护成本

对比项 tRPC Next.js API Routes
接口维护成本 低:类型定义和逻辑统一管理 高:前后端类型分离,容易错漏
路由定义 自动生成 /api/trpc 路由 每个 API 路由都需要手动定义
代码复用性 复用率高,前后端共用类型 前后端类型分离,重用性低
中间件 支持中间件,可统一管理请求 需手动实现 middleware
学习成本 中:需要学习 tRPC 的语法 低:熟悉 Next.js 的 fetch 即可

解释:

  • tRPC 的类型共享,意味着你不需要在前端和后端分别维护 API 类型。
  • Next.js API 中,前后端的类型独立,每次修改 API 需要同步更新前端的调用逻辑,成本更高。

🔥 5. 使用场景

场景 tRPC Next.js API Routes
简单的增删改查 ✅ 适用,简单高效,省去序列化 ✅ 适用,传统的 API 方式
端到端类型安全 ✅ 更好,类型全链路可用 ❌ 需手动定义类型
前后端协作 ✅ 最佳,前后端共享类型 ❌ 需手动同步类型
需要中间件支持 ✅ 支持,易于编写中间件 ❌ 需手动实现中间件
数据同步和缓存 ✅ 内置缓存 (React Query) ❌ 需自己实现 SWR/React Query
第三方 API 调用 ❌ 不擅长与外部 API 通信 ✅ 适用于 REST 和外部 API

总结:

  • tRPC 适合前后端共享类型的 SPA 项目,如:面试平台、管理面板、内部工具等。
  • Next.js API Routes 适合传统的 API 开发 ,需要与外部 API 通信,如:支付网关、外部 API 代理等。

🔍 6. 详细对比总结

对比项 tRPC Next.js API Routes
类型安全 ✅ 自动前后端类型一致 ❌ 需手动同步
API 语法 🚀 直接用 trpc.add.useQuery 需要 fetch('/api/add')
中间件支持 ✅ 轻松支持中间件 ❌ 需手动实现
数据序列化 🚀 tRPC 内部优化,序列化开销低 ❌ 必须手动 JSON 序列化
缓存 ✅ 通过 React Query 自动缓存 ❌ 需手动实现
调用方式 🚀 直接使用 React Hook ❌ 需 fetch
学习成本 ⚠️ 中,需要理解 tRPC 概念 ✅ 低,熟悉 REST 即可
SSR 支持 ✅ 支持 SSR 和 SSG ✅ 支持 SSR 和 SSG
推荐场景 🚀 适合前后端类型一致的 SPA 🚀 适合与外部 API 通信的场景

💡 总结

  • 如果前后端共用的类型一致性 对你来说非常重要,tRPC 是最好的选择,类型自动联动
  • 如果你需要与外部 REST API 交互 ,或需要与第三方 API 通信,那使用 Next.js 的 API Routes 会更自然。
  • tRPC 适合SPA 应用,面板管理系统,BFF(后端为前端)架构
  • Next.js API Routes 适合与外部系统的通信,比如 OAuth 登录、Webhook 等。

🚀 你应该怎么选择?

  • 如果你想要前后端共享类型、端到端的类型安全,选 tRPC。
  • 如果你更熟悉 REST API,并且需要与外部系统交互,选 Next.js API Routes。

创建一个基于tRPC的 Todo 应用

📦 1. 准备工作

确保你的项目已经完成了 tRPC 的基础配置(参考之前的教程),并安装了以下依赖:

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

📂 2. 项目结构

我们将在项目中新增文件来实现一个简单的 Todo 应用,更新后的结构如下:

arduino 复制代码
my-trpc-app/
├── app/
│   ├── page.tsx           // 主页,展示 Todo 列表
│   └── api/
│       └── trpc/
│           └── route.ts   // tRPC API 路由
├── server/
│   ├── trpc.ts            // tRPC 服务器配置
│   └── router/
│       ├── appRouter.ts   // 主路由
│       └── todoRouter.ts  // Todo 路由
├── utils/
│   └── trpc.ts            // tRPC 客户端配置
└── tsconfig.json          // TypeScript 配置

🔧 3. 配置 Todo 路由

文件server/router/todoRouter.ts

typescript 复制代码
import { router, procedure } from '../trpc';
import { z } from 'zod';

let todos: { id: number; title: string; completed: boolean }[] = [];

export const todoRouter = router({
  // 获取所有 Todo
  getAll: procedure.query(() => {
    return todos;
  }),

  // 添加 Todo
  add: procedure
    .input(z.object({ title: z.string() }))
    .mutation(({ input }) => {
      const newTodo = {
        id: todos.length + 1,
        title: input.title,
        completed: false,
      };
      todos.push(newTodo);
      return newTodo;
    }),

  // 切换完成状态
  toggle: procedure
    .input(z.object({ id: z.number() }))
    .mutation(({ input }) => {
      const todo = todos.find((t) => t.id === input.id);
      if (!todo) throw new Error('Todo not found');
      todo.completed = !todo.completed;
      return todo;
    }),

  // 删除 Todo
  delete: procedure
    .input(z.object({ id: z.number() }))
    .mutation(({ input }) => {
      todos = todos.filter((t) => t.id !== input.id);
      return { success: true };
    }),
});

🔗 4. 将 Todo 路由集成到主路由

文件server/router/appRouter.ts

typescript 复制代码
import { router } from '../trpc';
import { todoRouter } from './todoRouter';

export const appRouter = router({
  todo: todoRouter, // 注册 Todo 路由
});

// 导出 tRPC 路由的类型
export type AppRouter = typeof appRouter;

🖥️ 5. 在页面中调用 tRPC

文件app/page.tsx

javascript 复制代码
'use client';

import { trpc } from '@/utils/trpc';
import { useState } from 'react';

export default function Home() {
  const [newTodo, setNewTodo] = useState('');
  const utils = trpc.useContext();

  const { data: todos, isLoading } = trpc.todo.getAll.useQuery();
  const addTodo = trpc.todo.add.useMutation({
    onSuccess: () => {
      utils.todo.getAll.invalidate(); // 刷新列表
    },
  });
  const toggleTodo = trpc.todo.toggle.useMutation({
    onSuccess: () => {
      utils.todo.getAll.invalidate(); // 刷新列表
    },
  });
  const deleteTodo = trpc.todo.delete.useMutation({
    onSuccess: () => {
      utils.todo.getAll.invalidate(); // 刷新列表
    },
  });

  if (isLoading) return <p>加载中...</p>;

  return (
    <div style={{ padding: '20px' }}>
      <h1>Todo 应用</h1>

      <div style={{ marginBottom: '20px' }}>
        <input
          type="text"
          placeholder="新增 Todo"
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
        />
        <button
          onClick={() => {
            addTodo.mutate({ title: newTodo });
            setNewTodo('');
          }}
        >
          添加
        </button>
      </div>

      <ul>
        {todos?.map((todo) => (
          <li key={todo.id} style={{ marginBottom: '10px' }}>
            <span
              style={{
                textDecoration: todo.completed ? 'line-through' : 'none',
                cursor: 'pointer',
              }}
              onClick={() => toggleTodo.mutate({ id: todo.id })}
            >
              {todo.title}
            </span>
            <button onClick={() => deleteTodo.mutate({ id: todo.id })}>
              删除
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

💡 6. 测试应用

  1. 启动开发服务器:

    arduino 复制代码
    npm run dev
  2. 打开浏览器访问 http://localhost:3000/,你会看到一个简单的 Todo 应用,可以:

    • 添加新的 Todo 项目。
    • 切换 Todo 的完成状态。
    • 删除 Todo。

🎉 7. 总结

通过这个 Todo 应用,你已经学会了以下内容:

  • tRPC 查询与变更 :如何使用 querymutation
  • 端到端类型安全:通过 TypeScript,在前后端共享类型定义。
  • 状态管理 :通过 useContext 实现自动刷新列表。

下一步可以尝试优化 UX,例如添加加载状态和错误提示,或将数据持久化到数据库。

相关推荐
DARLING Zero two♡21 分钟前
Microi吾码低代码平台:前端源码的本地运行探索
前端·低代码·开源·c#·vue
m0_7482448323 分钟前
前端常用npm库大全-vue,react,通用(持续更新)
前端·vue.js·npm
stormsha24 分钟前
解决 Nginx 部署 React 项目时的重定向循环问题
前端·nginx·react.js
TANGLONG22225 分钟前
【初阶数据结构与算法】初阶数据结构总结之顺序表、单链表、双链表、栈、队列、二叉树顺序结构堆、二叉树链式结构(附源码)
java·c语言·数据结构·c++·python·算法·面试
MoonBit月兔26 分钟前
GitHub 正式收录 MoonBit 作为一门通用编程语言!核心用户突破三万!
开发语言·github·编程语言·moonbit
m0_748239831 小时前
一款免费、简单、快速的JS打印插件,web 打印组件,基于JavaScript开发,支持数据分组,快速分页批量预览,打印,转pdf,移动端,PC端
前端·javascript·pdf
一个W牛2 小时前
导入excel动态生成海报
前端·javascript·excel·贴图
samlyx2 小时前
BFC的理解
前端
不学习何以强国3 小时前
uni-app 个人课程表页面
前端·javascript·uni-app
Yui_NIE4 小时前
解决core-js报错:These dependencies were not found: core-js/modules/es.array.push.js
开发语言·前端·javascript