You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS tRPC TypeScript类型错误求助:void/undefined不兼容

TRPC + TypeScript 类型错误排查与解决

问题描述

开启TypeScript严格模式时,本地VS Code无报错,但部署到Vercel时构建失败;关闭严格模式后,VS Code出现两类明确的类型错误:

  • 调用trpc.useQuery传入id参数时:Type '{ id: number; }' is not assignable to type 'undefined'
  • 调用mutateAsync传入countdownId时:Argument of type '{ countdownId: number; }' is not assignable to parameter of type 'void'

代码示例

前端代码(admin.tsx)

import { trpc } from "../../utils/trpc";

function Admin(props: {id: number}) {
  // 错误1:Type '{ id: number; }' is not assignable to type 'undefined'
  const { data: countdown, isLoading } = trpc.useQuery([
    "countdown.getCountdownAdmin",
    { id: props.id },
  ]);

  const utils = trpc.useContext();
  // 关联错误2的mutation定义
  const mutateResults = trpc.useMutation(["countdown.updateResults"], {
    async onSuccess() {
      await utils.invalidateQueries(["countdown.getCountdownAdmin"]);
    },
  });

  // 错误2:Argument of type '{ countdownId: number; }' is not assignable to parameter of type 'void'
  async function generateResults() {
    const data = await mutateResults.mutateAsync({
      countdownId: countdown.id,
    });
  }

  return (
    {/* 无关代码省略 */}
  );
}

export default Admin;

服务端代码(countdown.ts)

import { TRPCError } from "@trpc/server";
import { createProtectedRouter } from "./protected-router";
import { z } from "zod";

export const countdownProtected = createProtectedRouter()
  .query("getCountdownAdmin", {
    input: z.object({ id: z.number() }),
    async resolve({ ctx, input }) {
      const { id } = input;
      try {
        const countdown = await ctx.prisma.countdowns.findUnique({
          where: { id },
          include: {
            UserCountdowns: {
              include: {
                User: { include: { Votes: { where: { countdownId: id } } } },
              },
            },
            Results: true,
            InviteCode: true,
          },
        });
        return countdown;
      } catch (e) {
        console.log(e);
        throw new TRPCError({ code: "BAD_REQUEST" });
      }
    },
  })
  .mutation("updateResults", {
    input: z.object({ countdownId: z.number() }),
    async resolve({ ctx, input }) {
      const { countdownId } = input;
      try {
        const data = await ctx.prisma.countdowns.findUnique({
          where: { id: countdownId },
          include: { Votes: { include: { User: true }, orderBy: { id: "asc" } } },
        });
        // 业务逻辑省略
      } catch (e) {
        console.log(e);
        throw new TRPCError({ code: "BAD_REQUEST" });
      }
    },
  });

解决方案

1. 修复TRPC客户端类型不匹配问题

两类错误的核心原因都是客户端未正确导入服务端的Router类型,导致TRPC无法自动推导query/mutation的输入输出类型。

步骤:

  • 在客户端的TRPC工具文件(如utils/trpc.ts)中,确保导入服务端的根Router类型,并创建带类型的TRPC客户端:
    import { createTRPCReact } from "@trpc/react-query";
    // 替换为你服务端根Router的实际路径
    import type { AppRouter } from "../server/routers/_app";
    
    export const trpc = createTRPCReact<AppRouter>();
    
  • 重新编译服务端代码,确保类型定义正确生成;若使用了TRPC的代码生成工具,执行生成命令更新类型。
  • 重启VS Code的TypeScript服务(快捷键Ctrl+Shift+P,选择TypeScript: Restart TS Server),清除类型缓存。

2. 处理countdown的undefined风险

useQuery返回的data在加载阶段为undefined,直接访问countdown.id会触发潜在的运行时错误,同时也会影响类型推导:

async function generateResults() {
  if (!countdown) return; // 先判断countdown是否存在,避免undefined访问
  const data = await mutateResults.mutateAsync({
    countdownId: countdown.id,
  });
}

3. 解决严格模式下本地与构建环境的差异

严格模式下Vercel构建失败但本地无报错,通常是因为:

  • 本地VS Code使用的TS版本与项目依赖的TS版本不一致:点击VS Code右下角状态栏的TS版本,选择Use Workspace Version。
  • 本地TS缓存未更新:删除node_modules/.cache目录,或重启TS服务。
  • 确保tsconfig.json的严格模式配置在本地和Vercel一致,比如设置"strict": true,避免环境差异导致的类型检查结果不同。

内容的提问来源于stack exchange,提问作者Karumu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 18:43:12