ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

T3 Stack实战:用t3code实现类型安全的全栈开发

T3 Stack实战:用t3code实现类型安全的全栈开发

去年我们团队有个内部工具前后端拆了两个仓,前端React、后端FastAPI,两个团队的人天天在联调接口上扯皮。后来我抽了一个周末,用项目“t3code”把整个链路重写了一遍——其实就是把T3 Stack的标准玩法落到一个真实项目里:Next.js当应用壳,tRPC做API层,Prisma管数据库,全程TypeScript贯通。这个t3code项目解决的核心问题只有一个:让类型从前端一路穿到数据库,中间不带断的。

说句实在话,起初我并不看好T3这一套,觉得它把太多东西绑定在一个框架里,自由度不够。但真正把代码写起来之后我才发现,这种“少做选择”的约束反而节约了大量脑力,尤其是对于需要独立交付全栈功能的人来说,它几乎把“前后端类型不一致”“接口文档过期”“数据库字段拼错”这一票常见问题全部压在萌芽状态。这篇文章我就拿t3code当例子,从选型动机、初始化流程、数据链路实现到排坑实录,完整过一遍。适合正在折腾全栈项目、被类型安全折磨过,或者单纯想看看t3code到底值不值得用的人。不管你是刚工作不久的新人,还是带过项目的老兵,读完应该都能直接照着搭一套自己的。

1. t3code是个什么东西:先聊聊我为什么折腾它

1.1 手头项目遇到的老问题

之前我们做一个内部管理系统,逻辑不算复杂,无非就是几十张表、若干后台页面、一些权限控制。但项目维护到中期,我明显感觉到成本开始失控。改动一个后端字段,前端接口类型要跟着改,接口文档也要改,跑起来还经常出现前端传了string、后端其实要number这种低级问题。当时我们靠 review 和人肉记忆来维持一致性,效率非常低。

这种“类型不一致”的问题,单看每个报错都很小,但架不住数量多、频率高。有一次因为后端把某个字段从available改成了status,前端漏改了三处,测试环境直接崩了半小时。类似的事情多了以后,我就在想,能不能有一种方案,把类型定义做成唯一的来源,让前后端、数据库全都共享同一份契约。t3code 这个名字听起来很唬人,实际上干的正是这件事。它是把几个工具按照一套成熟的模式组合起来,让类型在整条链路上自动传播,不需要你手写第二遍。

1.2 t3code想要一锅端的三个目标

我在设计 t3code 的时候给自己定了三个必须达成的目标,不是写代码时的口头愿望,而是后续每个模块都要遵守的硬性原则。

第一个目标是端到端类型安全。所谓“端到端”,不只是前端到后端,还要穿透数据库。比如你定义了一个 Todo 模型,那么在数据库、后端接口、前端组件里,这个 Todo 的结构应该完全一致。改模型的时候,所有用到它的地方都应该直接报类型错,而不是等到运行时炸给你看。第二个目标是减少样板代码。传统 REST 方案里,每个接口都要写路由、控制器、校验、返回类型,前端还要单独维护 API 封装,这些重复劳动能省就要省。第三个目标是保持技术栈收敛。项目里不要动不动就引入十几种框架,T3 的核心就是把 Next.js、TypeScript、tRPC、Prisma 这四样吃透,吃透它们之后,你就能覆盖绝大多数全栈场景,而不需要在不同工具中间来回切换。

这三个目标定了以后,后面所有选型都变得非常清晰。有人可能会说,这不就是后端少写几个接口嘛,有什么大不了的。但真正做过一年以上全栈维护的人会明白,省下的不只是代码量,而是你脑子里那个“维护多份契约”的负担。

2. 技术选型拆解:T3 Stack的四根柱子

2.1 Next.js:应用框架为什么不是Vite或纯React

t3code 最外层其实是 Next.js,这一点我一开始也不太理解,毕竟用 Vite 搭 React 项目要轻快得多。实际用下来才知道,Next.js 在这里承担的不只是打包工具,而是一个完整的应用壳子:文件即路由、服务端渲染、API 路由、中间件、静态资源优化,这些能力直接内建,不需要我再额外组织一套架构。

在 t3code 这种偏全栈的项目里,我更看重它的服务端组件能力。很多数据获取逻辑可以直接放在 Server Component 里跑,数据库查询在服务端完成,然后直接把带数据的结果渲染成页面。这样前端拿到的 HTML 本身就包含内容,对首屏速度和 SEO 都比较友好。如果你用纯 React 加 Vite,这些事情就得自己去搭 BFF(Backend For Frontend)层,复杂度马上上来。

还有一点很值得说:Next.js 本身对 TypeScript 的支持是近乎原生的。新项目的 tsconfig 是自动生成的,导入路径可以配置别名,构建时自动做类型检查。虽然这些功能通过手动配置也能在其他框架里实现,但“开箱即用”这件事本身就意味着少踩坑。对于我这个想要快速跑通链路的人来说,少一个配置项就少一个出错的机会。

2.2 TypeScript:类型是一切的基础

T3 Stack 的第二个 T 就是 TypeScript,这个名字虽然常见,但在 t3code 里它不再是锦上添花的工具,而是所有类型安全的基石。你可以把 TypeScript 理解成一根贯穿全项目的“导管”,它本身不提供数据,但是它保证数据在流动的过程中不会变形。

t3code 里的数据从 Prisma 模型开始,Prisma 会根据你定义的schema.prisma自动生成类型;tRPC 的 router 在定义输入输出时也使用 TypeScript 类型;前端调用 tRPC 的 hook 时,类型又会从服务端推导过来。这一整条链路上,类型是自动流动的,你不需要在任何一层手动标注“这个接口返回什么类型”。这正是 TypeScript 在 t3code 里最重要的价值:它把类型变成了项目的“重力”,所有数据天然向下兼容。

很多新手会忽略 TypeScript 配置细节,比如strict模式。t3code 默认开严格模式,一开始写代码会有点痛苦,因为一些隐式的any都会变成红色波浪线。但别急着关掉它,严格模式下的报错绝大多数是真实隐患。我见过太多项目因为关闭 strict 而埋下隐患,最后 Bug 查半天才发现是一个类型写得太宽导致的。在 t3code 的语境下,严格的 TypeScript 不是给你添堵的,它是整个类型安全链路的地基。

2.3 tRPC:被低估的API通信方案

tRPC 是 T3 Stack 里最容易被误解的一个组件。很多人的第一反应是“不用 REST 吗?不用 GraphQL 吗?”我只想说,这取决于你的场景。t3code 是单仓库的全栈项目,前端和后端在同一个代码库、同一个进程里运行,这时候 REST 的“解耦”优势其实并没有那么明显,反而增加了类型重复维护的成本。

tRPC 的核心原理是“直接导出函数”,前端调用 tRPC 的 hook,实际上就是调用了一个位于服务端的 procedure。所有输入输出类型都从服务端自动推导到客户端,你不需要手写 API 文档,也不需要手动声明返回类型。这种体验有点像在写本地函数调用,只不过这个函数跑在服务端。

有人会担心这种写法不安全,其实 tRPC 本身只是传输协议的封装,验证逻辑还是要靠 zod 来处理。t3code 里每个输入参数都可以用 zod 定义格式,比如字符串长度、邮箱格式、数字范围,校验不通过就直接拒绝调用。比起 REST 方案里“参数对不对全靠后端心情”,tRPC 的方式反而更严谨。

2.4 Prisma:数据库层的最后一块拼图

数据库这块,t3code 选择了 Prisma。说实话,在 ORM 满天飞的时代,选择一个好用的 ORM 比想象中难。Prisma 的一大优势是 schema 即定义,你在schema.prisma里写清楚模型和关系,它就能自动生成完整的类型定义和查询客户端。数据库在你的代码里不再是“一堆字符串拼接的 SQL”,而是一个有类型的对象。

我在 t3code 里这么理解 Prisma 的角色:它是 TypeScript 类型安全链路的最后一公里。前面说到类型从服务端推导到前端,那服务端的类型又从哪来呢?很大一部分就是从 Prisma client 来。当你写ctx.db.todo.findMany()的时候,返回结果自动就是Todo[],不用手动标注。而当你把Todo类型传到前端时,tRPC 会保证这个类型的完整性。这种“处处有类型”的体验,用习惯了之后真的回不去老一套。

Prisma 还有一个我很喜欢的地方是迁移流程。传统开发里,改表结构通常要自己写 SQL 迁移,还要维护 migration 历史。Prisma 可以直接根据 schema 的变化生成迁移文件,虽然复杂场景下仍然需要手工调整,但大多数常规改动它都能自动处理,省了很多琐碎活。

3. 实操初始化:从零搭一个t3code项目

3.1 环境准备与create-t3-app交互

如果你也想搭一个属于自己的 t3code,最直接的方式是使用官方脚手架工具。我当时的操作步骤非常简单,首先是确认环境,Node.js 版本建议至少 18,我本机当时是 20,除了版本号之外基本没有额外要求。

npx create-t3-app@latest t3code

命令跑起来之后会进入交互界面,它会问你几个选择题。第一项是 TypeScript,默认开启,当然选上,这是整个体系的核心。第二项是 Tailwind CSS,如果你打算写界面就选上,不写纯接口项目也可以去掉。第三项是 tRPC,第四项是 Prisma,这两个是 t3code 的灵魂,必须勾选。还有 NextAuth.js 用于认证,如果系统里需要用户登录就选上,不需要可以先跳过。最后是一些可选项,比如 ESLint、Prettier、Docker,我建议 ESLint 和 Prettier 都选上,它们不增加多少成本,但对代码整洁度的帮助很大。

初始化完成后,它会自动安装依赖。这时候你可能需要等一段时间,因为 Next.js 加 Prisma 加 tRPC 的依赖树并不小。装完之后不要急着改代码,先进项目里看一眼基础结构,确认所有接缝都对上了再开始动手。

3.2 目录结构:每一层应该放在哪里

t3code 初始化之后的目录结构非常清晰,它其实暗示了你应该怎么组织代码。最核心的约定是这样的:

  • src/app用于放页面路由,Next.js App Router 的文件夹结构,这里每个文件夹对应一个 URL。
  • src/server是服务端逻辑的集中地,包括api/routers、db、auth这些。
  • src/trpc是 tRPC 客户端调用用的 hook 封装,前端组件从这里 import。
  • prisma目录放着schema.prisma,这是数据库模型定义的地方。

这么一分层,整个项目的职责就非常清楚了:页面只管页面,服务端只管服务端,数据库模型单独放在最底层。就算是一个新接手的人,也能很快根据路径判断一段代码属于哪一层。我在刚开始用的时候其实不习惯这种约束,觉得文件夹多了反而繁琐,但后来维护了几个月才发现,这种明确的路由式结构让你定位问题特别快,比如类型没对上,你就会自然去查 server 端的 router,而不是在几十个随意命名的文件里翻来找去。

有一点特别提醒:不要在src/app里直接写数据库查询。虽然 App Router 的 Server Component 确实允许你这么做,但长期来看,数据访问逻辑分散在页面组件里会很难维护。t3code 的理念是让 tRPC router 统一承担接口职责,页面只管调用,这样即使以后要加缓存、做登录校验,都只需要在 router 层统一处理,不用一个页面一个页面去改。

3.3 依赖取舍:什么该装什么不该装

T3 社区有一句话叫“你不需要的不装”,这也是 T3 Stack 的核心理由。t3code 作为一个样板项目,初始化之后会带一些基础依赖,但你不应该把它们当成必需品,而是要根据业务场景做一次清理。

举个例子,NextAuth.js 如果被勾选,项目里就会出现auth.ts和一系列的 session 配置。但如果你做的只是一个内部工具,用户体系还很模糊,那先不引入认证反而更轻量。又比如 Tailwind CSS,如果你要写的页面主要是后台表格和数据展示,那么 Tailwind 会是效率神器;但如果你有设计系统且样式主要由组件库接管,那么 Tailwind 可能会和组件库的样式方案冲突,这时候就可以考虑不放进来。

还有一个容易被忽略的点:格式化工具和 lint 工具。我强烈建议保留 Prettier 和 ESLint,因为 t3code 里类型是自动推导的,如果代码风格混乱,很容易出现“类型没变但 diff 巨大”的情况。统一格式化之后,协作体验会好很多。至于 Docker、Playwright 这些,如果项目还不到需要容器化和端到端测试的规模,完全可以先不装,等有需要再加也不迟。

4. 核心链路实现:数据库→API→页面一步到位

4.1 数据模型设计:从一个Todo开始

t3code 的典型示例往往从一个简单的 Todo 应用开始,我也不例外。因为 Todo 虽然业务简单,却能完整展示“数据库模型 -> tRPC router -> 前端页面”的全链路。先打开prisma/schema.prisma,定义一个最基础的模型:

model Todo { id String @id @default(cuid()) title String completed Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }

这里我选择String类型的 id 并使用cuid()作为默认值,而不是自增数字。原因是 t3code 这种网络应用里,暴露自增 id 很容易被遍历,而 cuid 基本不可预测,也能在分布式环境下保持唯一性。如果你只是纯内部工具,自增 id 也不是不行,但养成用 cuid 的习惯没有坏处。

定义好模型之后,需要让数据库和 Prisma client 跟上进度。t3code 默认使用 SQLite 作为本地开发数据库,方便快速起跑。我用的是 Postgres,所以先修改了.env里的数据库连接串,然后依次执行两条命令:

npx prisma migrate dev --name init_todo npx prisma generate

第一条命令会根据 schema 生成迁移文件并应用到数据库,第二条命令会重新生成 Prisma client 类型。这两条命令在后续修改模型的每一次都要用到,而且顺序一定不能反,先迁移再生成,否则你会拿到一份过时的 client 类型,代码里只会出现一堆诡异报错。

4.2 数据访问:Prisma client挂到上下文

在 t3code 里,Prisma client 不是每个页面各自创建,而是统一挂到一个全局上下文。这个上下文会在每次 tRPC 请求时被注入到 procedure 里,你只需要在 router 里通过ctx.db访问数据库即可。

初始化模板里的src/server/db.ts通常是这样的:

import { PrismaClient } from "@prisma/client"; const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient }; export const db = globalForPrisma.prisma ?? new PrismaClient({ log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], }); if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db;

这段代码的精髓在于:开发环境下热更新会反复执行模块,如果不把 PrismaClient 缓存到全局对象上,你很快就会看到“太多数据库连接”的报错。我最初没注意这个问题,结果每次保存文件都能看到几十个连接堆积,数据库直接被拖垮。记住这种写法,这不是可选项,而是 Prisma 配合热更新的标准姿势。

4.3 业务接口:tRPC router的定义与校验

数据库层准备好后,接下来就是定义业务接口。t3code 的惯例是在src/server/api/routers下按业务域建文件,比如todo.ts。我先写一个最基础的 router,包含查询和创建两个 procedure:

import { z } from "zod"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; export const todoRouter = createTRPCRouter({ getAll: publicProcedure.query(({ ctx }) => { return ctx.db.todo.findMany({ orderBy: { createdAt: "desc" }, }); }), create: publicProcedure .input(z.object({ title: z.string().min(1).max(100) })) .mutation(({ ctx, input }) => { return ctx.db.todo.create({ data: { title: input.title }, }); }), });

这里有个细节很值得展开说。getAll没有input,它是一个普通查询;create用.input()声明入参结构,并用 zod 定义了规则:字符串、最小长度 1、最大长度 100。这意味着如果有人传一个 200 字的标题,请求会在进入业务逻辑之前直接被拒绝,永远到不了数据库层。这种“边界校验”放在接口入口,而不是业务代码内部,是我非常推崇的做法。

你可能注意到我没有为create的返回值做任何注解,因为 tRPC 会自动根据 mutation 的结果推断出类型。当我前端调用同一个 mutation 时,拿到的data就已经是Todo类型,不需要手动声明接口返回结构。这个特点在复杂项目中特别宝贵,因为每次模型变化,前端类型都会自动跟着变,不会出现“文档还没更新”的情况。

写完 router 之后,记得去根 router 里挂载一下。模板里的root.ts会有类似createCallerFactory的机制,你只需要把todoRouter加进appRouter的集合里就行。如果忘了挂载,前端无论如何也调用不到这个新接口,而且这类报错往往不明显,排查半天才发现只是没挂。

4.4 页面接入:前端组件如何消费接口

tRPC 的一大爽点体现在前端调用上。模板已经在src/trpc/react.tsx里建好了 Provider,你直接在任意页面组件里用api这个对象来发起请求。以 t3code 的前端页面为例,写一个简单的待办列表:

"use client"; import { api } from "~/trpc/react"; export function TodoList() { const utils = api.useUtils(); const { data: todos, isLoading } = api.todo.getAll.useQuery(); const createTodo = api.todo.create.useMutation({ onSuccess: async () => { await utils.todo.getAll.invalidate(); }, }); if (isLoading) return <div>加载中...</div>; return ( <ul> {todos?.map((todo) => ( <li key={todo.id}> <span>{todo.title}</span> <span>{todo.completed ? "已完成" : "未完成"}</span> </li> ))} </ul> ); }

这段代码里最有价值的两行是api.todo.getAll.useQuery()和createTodo的 mutation。你不需要手动写 fetch、axios,也不需要自己在useEffect里管状态。useQuery自带 loading、error、data 三个状态,mutation 提交成功后,调用invalidate()可以让列表自动刷新。这种把数据请求状态机整个封装掉的方式,写起来有点像“前端直接调用前端函数”,但实际数据的传输却是真实发生在服务端的。

关于失效缓存,我多说一句:不要在 mutation 之后盲目刷新整个页面。用invalidate()让对应 key 的 query 重新失效,tRPC 会主动重新请求数据,页面不会整页重载。这个机制用熟了之后,你会发现交互表现比手动刷新页面要顺滑得多。

5. 常见问题与排查记录

5.1 类型报警:prisma generate没跑

t3code 项目里最常见的翻车点,我几乎每隔一阵子都会碰到一次。修改了schema.prisma之后,新建的字段或者模型在代码里一直报“Property does not exist”。原因通常是执行了迁移,但忘了重新生成 Prisma client。Prisma client 的类型是从生成的代码里读取的,你光改 schema 不动 client,TypeScript 根本不知道新字段的存在。

解决办法很简单,改完模型后把这两条命令跑齐:

npx prisma migrate dev npx prisma generate

建议把这两条命令搓成一条 npm script,比如"db:sync": "prisma migrate dev && prisma generate",以后改表模型就一行命令搞定,别给自己留下漏跑的机会。

5.2 环境变量没生效:Next.js的配置文件

第二个高频坑是环境变量。t3code 初始化之后会生成一个.env文件,里面写着DATABASE_URL、NEXTAUTH_SECRET等变量。但这个文件默认不会提交到 git,如果你是从仓库 clone 的代码,很容易遇到数据库连不上、认证报错等问题。

还有一个容易忽略的点:Next.js 在读取.env时有个规则,只有以NEXT_PUBLIC_开头的前端环境变量才会被打包进客户端代码。如果你想让前端直接读取某个变量,又没有加前缀,那前端拿到的永远是undefined。在 t3code 里我尽量不把环境变量暴露给前端,所有机密信息都放在服务端读取。如果确实需要前端感知某个配置,就在变量名上明确写出NEXT_PUBLIC_,避免自己和同事以后产生困惑。

5.3 热更新时DB连接爆掉

这个问题我在第三节已经提到,但值得再强调一遍。开发模式下,Next.js 的 Fast Refresh 会频繁重新执行模块,如果你的 PrismaClient 没有被缓存到全局,每执行一次模块就会新建一个连接,很快数据库连接池就会被打满。远程数据库尤其严重,有时候你只是改了十几个文件,数据库就提示 too many connections。

解决方式就是模板里那几行全局缓存代码,切勿删掉。如果你已经踩坑了,除了恢复缓存逻辑,还需要去数据库里把旧的连接手动清理一下,或者重启数据库服务。这个坑很难从表面看出来,因为代码逻辑完全正常,数据库却突然拒绝连接。

5.4 部署时想禁用tRPC的开箱报错

还有一个很微妙的点:t3code 项目在本地开发时一切正常,但部署到服务器后,接口频繁报 500 错误,原因往往不是业务逻辑,而是服务端环境的限制。比如你的服务端使用了 SQLite 数据库,而部署平台没有安装适合 SQLite 的底层依赖,Prisma 可能无法初始化。

如果代码里出现了类似PrismaClientInitializationError的报错,先检查数据库连接串在部署环境里是否可访问,防火墙、白名单这些都要排查。其次,如果部署环境是无服务器(serverless),要注意 tRPC 和 Prisma 有没有做长连接优化。无服务器平台每个请求可能都会创建新的实例,这时用serverless版本的 Prisma client 会有帮助。不过这些属于进阶话题,如果你的项目只是小规模部署,优先把数据库连接串配正确,大多数问题都能解决。

5.5 排查速查表

我把常见问题整理成一张表格,方便以后直接对照:

症状大概率原因排查命令/手段
字段类型报错Prisma client 未重新生成执行npx prisma generate
数据库连接失败环境变量未配置或连接串错误检查.env里的DATABASE_URL
接口 404tRPC router 未挂载检查root.ts是否引入对应 router
前端拿不到变量变量名缺少NEXT_PUBLIC_前缀确认变量前缀是否正确
连接数过多PrismaClient 未做全局缓存使用 global 对象缓存 Prisma client
mutation 后列表不更新忘记 invalidate 对应 query在 onSuccess 中调用invalidate()

这张表其实覆盖了我自己在 t3code 上遇到过的大部分问题,也基本覆盖了日常社区里高频出现的问题。如果开发中遇到类似的报错,优先从这张表里找答案,通常能省下大把排查时间。

6. 后续扩展思路与个人心得

6.1 给t3code加上认证

前面提到我初始化的时候没有选 NextAuth.js,但后来业务增长,我需要区分普通用户和管理员,认证这块还是得补上。如果你一开始就在 t3code 里集成了 NextAuth.js,那么只需要在 tRPC router 里把publicProcedure换成protectedProcedure,就能实现“必须登录才能访问”的效果。这个保护不是在前端页面做做样子,而是服务端真正校验了 session。

如果项目已经跑起来之后再去补认证,过程也不复杂。安装依赖、生成密钥、配置 provider,然后把auth.ts里导出的getServerSession挂到 tRPC 上下文里。所有步骤做完之后,再回到 router 里用ctx.session判断用户身份。这算是我比较推荐的演进路线:先跑通核心业务,再逐步加上权限控制,而不是一开始就被认证逻辑绑架。

不过有一点要注意,认证只是安全的一环,不是全部。如果你做的是面向公网的产品,输入校验、访问频率控制、CSRF 防护这些都不能省。t3code 的 zod 校验能挡住格式不对的请求,但它挡不住所有恶意流量。

6.2 从t3code转向更复杂业务时的架构调整

当项目规模变大之后,你可能会发现把所有 router 塞在一个 app 里有点拥挤。我现在的做法是按业务域拆分文件:用户相关的进userRouter,订单相关的进orderRouter,每个 router 里尽量只处理自己域里的数据。如果两个域之间存在重复逻辑,我会把公共逻辑抽到src/server/api/services目录下,而不是在 router 里写大段冗余代码。

同时,查询层面的性能优化也要提上日程。Prisma 默认查询会加载所有字段,如果你只需要列表里的三个字段,可以用select限定。复杂的过滤和分页,应该在 tRPC 的input里定义好参数,然后传给 Prisma 查询条件。不要在前端一次性拉全部数据再 filter,数据的筛选必须尽量推给数据库执行,这样接口响应才能更快。

6.3 关于什么时候别用T3的实话

最后聊点不常被人提起的实话。T3 Stack 很好用,但它并不是万能的,这也是我在反复实践后得出的个人结论。如果你需要的是一个“API 优先”的架构,比如你的服务不只是给网页用,还要给小程序、移动端甚至第三方开放接口,那么 tRPC 这种和前端深度绑定的方案就不太合适,你可能更需要一个独立的 REST 或 GraphQL Gateway。同样,如果你的团队前端和后端是严格隔离的,前端不能使用 Node.js 进程内的调用方式,那么 t3code 这种模式也需要调整。

t3code 这个项目对我来说最大的意义,不是它用了多前沿的技术,而是它证明了“类型安全”这件事完全可以成为日常开发的默认状态。以前我要花大量精力维护接口文档和类型定义,现在这些都可以自动完成。每次模型变更、前端报错、类型提示,这套体系都能第一时间告诉我问题在哪一层,我只需要跟着编译器走,很多隐藏的问题在运行之前就已经被排除掉了。

如果你也被前后端联调、类型不一致、接口文档过期这些问题折磨过,我很建议你亲手搭一个 t3code 试试。不用一上来就上大项目,就从最简单的 Todo 开始,把数据从数据库按“有类型”的方式推到页面上,你很快就能感受到这种开发模式的特别之处。对我来说,用过类型贯通的全栈方案之后,再回到老路已经不太可能了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进