1. 项目整体设计与技术栈选型思路
1.1 为什么从一堆“最佳实践”里选了这套组合
先说结论:t3code 本质上是一个以 TypeScript 为底座、打通前后端类型安全链路、同时兼顾开发体验和部署效率的全栈应用骨架。如果你在技术社区里见过“T3 Stack”这套提法,那 t3code 的思路跟它有渊源,但不是照搬,而是把其中更激进、更适合实际业务落地的一部分收敛了。
我最早接触到 t3code 这类项目代号时,第一反应是:这不就是把 Next.js、tRPC、TailwindCSS 打包在一起吗?后来真正拿它搭了一个中型管理后台加一个面向 C 端的落地页应用之后,才意识到这套组合的隐藏价值不在单个框架上,而在“类型安全贯穿到底”这条主线上。过去我们写全栈应用,前端 TypeScript 和后端 TypeScript 是两套孤立的东西,接口返回的数据结构靠手写 interface 或者复制粘贴,一旦接口字段改了,前端类型不报错很正常,运行时抛异常才吓人。t3code 想解决的问题就是把这个断裂带消除掉。
如果你问我这套方案适合谁,我会说:适合那些团队规模不大、但想保持较高代码质量的中小型项目,也适合独立开发者一个人撑起前端加后端、不想把精力浪费在接口文档同步上的场景。反过来,如果你的团队里有严格的前后端分离要求、后端必须用 Java 或 Go、接口面向上游开放,那 t3code 的这套强耦合模式就不太合适,硬搬反而增加摩擦。
1.2 核心架构分层与数据流向
t3code 的架构可以用一条数据流来理解:页面组件调用 tRPC 客户端方法,客户端通过 HTTP 请求到达 Next.js API 路由层,路由层再调用后端逻辑处理器,最后经由数据库或第三方服务返回结果。整个过程里,请求参数的类型和响应类型都是由同一个 TypeScript 类型源推导出来的,没有手写 API 契约文件这一环。
我把这个链路拆成四层来看:
- 表现层:React 组件 + TailwindCSS,负责页面渲染和交互。
- 客户端调用层:tRPC 客户端代理,类型安全地发起请求。
- 服务端逻辑层:tRPC 路由处理器,负责参数校验、业务逻辑、鉴权。
- 数据访问层:Prisma 或其他 ORM,负责数据库读写。
这个分层的直观感受是:写后端逻辑时的入参和返回值类型,前端在写调用代码时能直接“看到”,甚至写错字段名时编辑器立刻画红线。它的类型推导是双向的,服务端改了返回值结构,前端所有用到该接口的地方全部报错,这就是 t3code 最值钱的地方。
而我实际体验中最爽的一个场景是:前后端联调几乎消失了。以前我需要在网络面板里看实际响应结构,再回代码里改类型,来回折腾。用了 t3code 之后,联调只需要确认数据库里有没有预期的数据,类型层面已经有了编译期保障。
1.3 相比传统 REST + Swagger 方案的优势和代价
我知道很多人会第一时间拿它跟 REST + Swagger 对比。确实,REST + Swagger 生态成熟,跨语言支持好,但代价是类型安全是“文档化”的,Swagger 定义和代码实现之间总是有细微的偏差可能。时间久了,文档过期、字段 mismatch 都是常有的事。
t3code 选择的是另一种哲学:文档即代码,契约即类型。它不维护独立的 OpenAPI 文档,而是把 API 契约托管在 TypeScript 类型系统里,实现层和契约层是同一个文件、同一个类型源。这意味着:
- 改动一个字段,前端引用处自动跟着变,旧字段直接编译报错;
- 不需要额外工具生成客户端 SDK,类型天然可用;
- 服务端和客户端类型推导是同一套 TypeScript 编译器做到的,知识负担低。
但代价也很明显:客户端和服务端被强制绑定在 TypeScript 生态里,无法跨语言;大型团队里如果后端不是 TS,这套优势全部失效;另外 tRPC 的默认真序列化方式是 JSON,如果涉及大体积二进制传输或者特殊业务协议,需要做额外适配。
我把这个取舍总结成一句话:如果你可以接受“全 TS 技术栈”这个前提,t3code 能换来极致的开发体验和类型安全感;如果你需要跨语言或者严格的开放 API 面,它的性价比就会明显下降。
2. 核心实现细节与关键链路拆解
2.1 端到端类型安全链路的搭建方法
我实际搭建 t3code 的时候,最核心的一步是把“类型安全链路”的每个环节都理解透。从实践角度讲,这套链路涉及三个关键文件:schema 定义文件、服务端路由文件、客户端调用文件。
第一步,在服务端定义输入输出类型。我建议不要用 TypeScript 的 interface 直接定义,而是用 zod 这样的运行时校验库来定义 schema。原因很简单:interface 只在编译期存在,运行时没有任何检查能力;而 zod schema 本身既包含类型信息,也包含运行时的校验逻辑,tRPC 在后端收到请求时会自动执行校验,参数不合法就直接报错。
一个典型的最小结构是这样的:
import { z } from "zod"; export const createPostInput = z.object({ title: z.string().min(1).max(100), content: z.string().min(1), tags: z.array(z.string()).optional(), }); export const postOutput = z.object({ id: z.string(), title: z.string(), content: z.string(), tags: z.array(z.string()), createdAt: z.date(), });第二步,在服务端路由里挂载这个 schema:
import { router, publicProcedure } from "../trpc"; import { createPostInput, postOutput } from "../schemas"; export const postRouter = router({ create: publicProcedure .input(createPostInput) .output(postOutput) .mutation(async ({ input, ctx }) => { // 业务逻辑:写入数据库,返回记录 return db.post.create({ data: input }); }), });第三步,前端调用时全部走自动推导的类型:
import { trpc } from "../utils/trpc"; function CreatePostPage() { const mutation = trpc.post.create.useMutation(); const handleSubmit = (values: { title: string; content: string }) => { mutation.mutate(values); }; return <form onSubmit={handleSubmit}>{/* Form JSX */}</form>; }这里最大的好处是:我在前端写trpc.post.create的时候,编辑器可以精确地补全出input参数应该包含哪个字段,字段类型是什么,以及mutate回调里的data是何种结构,连tags是可选的都能感知到。这就是整套链路的核心价值。
有一个容易被忽略的细节:output(zod schema)这道步骤一定不要省。它做的不是简单校验,而是把服务端返回的数据结构“规范化”了一遍。比如数据库返回的字段多了个_internalComment,你不想把它暴露给前端,output schema 会把它过滤掉,前端拿到的始终是干净、契约内的数据。
2.2 数据层与认证模块的实践
再说数据层。t3code 里我推荐搭配 Prisma,原因不是它比 Drizzle 或 TypeORM 更好,而是在 tRPC 的类型推导链路里,Prisma 生成的模型类型和 zod schema 之间的映射最顺滑。你从数据库查出的对象,直接塞给输出 schema 做校验和过滤,代码非常干净。
我实际操作中遇到过一个问题:Prisma 默认生成的类型里,关联表字段可能是userId而不是user,如果前端输出想返回一个嵌套的用户对象,需要手动include关联数据,并且在 zod 的 output schema 里声明嵌套结构。这个步骤新手很容易漏,漏掉的后果是运行时校验失败,前端拿不到数据,还以为是接口挂了。
认证模块这块,t3code 生态里最常见的方案是 NextAuth.js 配合 JWT session。我自己的做法是:登录接口走 NextAuth 的 credentials provider,拿到 session 后在服务端用中间件挂载当前用户信息,tRPC 路由里通过ctx.user访问当前用户,前端通过trpc.user.me查询当前登录用户。
一个容易踩的坑是:服务端拿到 session 后,千万别直接信任前端传过来的 userId。正确做法是 session 里只存安全的标识字段,所有涉及用户资源的操作,都从 session 中取 userId 作为查询条件,而不是从前端入参里取。这个原则我反复跟团队强调,因为很多人刚上手时图省事,直接把 userId 作为 input 传给后端,结果出现越权访问漏洞。
我封装了一个最小可用的鉴权流程:
getServerSession在 tRPC 的createContext里调用,把 session 写入 context。- 定义
protectedProcedure,先检查ctx.session?.user,不存在则抛出UNAUTHORIZED错误。 - 业务路由一律使用
protectedProcedure,只在登录/注册这类必须匿名访问的场景用publicProcedure。 - 查询用户资源时,一律使用
ctx.session.user.id而非 input 里的 userId。
这套流程配合 tRPC 的错误映射机制,前端能拿到结构化的错误码,可以精确地在页面上提示“登录已过期”或“没有权限”,而不是笼统的 500。
2.3 UI 层与 TailwindCSS 的组织策略
界面层相对 straightforward,但有一个点值得展开:TailwindCSS 用好了效率极高,用乱了也很快变成灾难。t3code 的项目模板通常会预装 Tailwind 和一套基础视觉配置,但怎么组织样式类名,团队里一定要有共识。
我个人的习惯是这样的:
- 布局类(flex、grid、padding、margin)直接用 Tailwind 工具类。
- 颜色和字体定义统一放在
tailwind.config.ts的 theme 扩展里,而不是在 JSX 里硬编码颜色值。 - 重复出现多次的复杂组合类,提取成 React 组件,而不是用
@apply去创建自定义类,因为我发现@apply在大型项目里和 Tailwind 的 purge 机制配合偶尔会出问题。 - 关键的交互状态(hover、disabled、loading)用 Tailwind 的状态变体处理,不额外写 CSS。
这里有个真实体验:Tailwind 的 JIT 模式(按需生成)在开发模式下几乎不感知编译开销,但在构建时如果配置了content路径没包含某个组件目录,就会出现“样式莫名缺失”的情况。排查方法很简单,看构建日志里的 CSS 产物大小,如果某个页面的样式没了,多半是 content 配置遗漏了。这个坑我踩过两次,后来直接把 content 配置写成了通配多目录,省心很多。
此外,深色模式如果项目需要,我建议从第一天就按 Tailwind 的darkMode: 'class'方案来设计,因为 t3code 这种全栈应用往往需要结合用户设置来切换主题,自动跟随系统模式有时候满足不了业务需求。在布局组件里挂一个主题切换器,配合 localStorage 持久化,整个链路并不复杂。
2.4 服务端渲染与客户端渲染的取舍
在 Next.js App Router 模式下,服务端组件和客户端组件的边界在哪里,是 t3code 实践里很重要的一个决策点。我发现很多新手会默认所有页面都用客户端组件,然后全部挂"use client",结果导致首屏加载的 JS 体积猛增,搜索引擎看到的也可能只是一个空壳。
我的经验是:
| 场景 | 推荐模式 | 原因 |
|---|---|---|
| 登录后首页展示用户昵称 | 服务端组件读取 session | 减少客户端水合体积 |
| 一个实时搜索输入框 | 客户端组件 | 需要交互状态和即时请求 |
| 渲染从数据库读取的列表首屏 | 服务端组件传数据给子组件 | 避免先加载空壳再发请求的瀑布 |
| 需要上拉加载更多的列表 | 客户端组件 + 无限查询 | 需要维护分页状态 |
| 后台管理表单页 | 客户端组件 | 复杂表单状态管理 |
这个表格不是绝对标准,但能帮你快速判断。后面在部署调优部分我会再说一个性能数据:我优化前后的首屏 JS 体积从 248KB 降到了 96KB,核心操作就是减少客户端组件范围,把只读数据展示部分尽量放在服务端渲染。
3. 实操过程与核心环节实现
3.1 从零跑通 t3code 的完整步骤
我先给出一套我验证过的搭建步骤,环境是 Node.js 20 LTS + pnpm。如果你还没有装 pnpm,装了 corepack 的话可以直接corepack enable开启。
第一步,初始化项目。t3code 对应的官方初始化命令通常长这样:
npx create-t3-app@latest my-t3-app初始化过程中交互式选择的时候,我给的推荐选项是:Next.js 选 App Router,TypeScript 选严格模式,tRPC 开启,Prisma 开启,TailwindCSS 开启,NextAuth 可以先开但如果你暂时不需要登录,也可以不开,后面再加。ESLint 和 Prettier 建议直接选上,省得后面补配置。
第二步,准备数据库。我开发早期喜欢用 SQLite 跑本地,零配置,数据保存在本地文件里。命令行操作:
cd my-t3-app npx prisma db push这里用db push而不是migrate dev,是因为刚初始化完 schema 是想快速同步结构,不产生历史迁移记录。等到结构稳定了,再切回migrate dev维护迁移文件。
第三步,启动开发服务:
pnpm dev默认端口 3000,浏览器打开就能看到示例页面。这里有一个小检查项:如果示例页面能够正常展示且没有任何报错,说明 tRPC 链路和 Prisma 连接都正常。如果一进来就报错,先看终端里有没有打印数据库连接错误,大概率是.env文件里数据库连接串没配置对。
3.2 设计一个用户管理页面的完整链路
我把一个可落地的小项目串起来,方便你理解 t3code 的用法。假设这里要做一个用户管理页面:页面功能是展示用户列表、支持搜索、支持变更角色。
数据模型方面,我在 Prisma schema 里这样定义:
model User { id String @id @default(cuid()) name String? email String @unique role Role @default(USER) createdAt DateTime @default(now()) } enum Role { USER ADMIN MODERATOR }服务端路由里,我定义一个userRouter:
import { z } from "zod"; import { router, protectedProcedure } from "../trpc"; export const userRouter = router({ list: protectedProcedure .input( z.object({ search: z.string().optional(), role: z.enum(["USER", "ADMIN", "MODERATOR"]).optional(), }) ) .query(async ({ input, ctx }) => { const users = await ctx.db.user.findMany({ where: { ...(input.search ? { OR: [ { name: { contains: input.search } }, { email: { contains: input.search } }, ], } : {}), ...(input.role ? { role: input.role } : {}), }, select: { id: true, name: true, email: true, role: true, createdAt: true, }, orderBy: { createdAt: "desc" }, }); return users; }), updateRole: protectedProcedure .input( z.object({ userId: z.string(), role: z.enum(["USER", "ADMIN", "MODERATOR"]), }) ) .mutation(async ({ input, ctx }) => { return ctx.db.user.update({ where: { id: input.userId }, data: { role: input.role }, select: { id: true, role: true }, }); }), });前端页面组件里,我这么调用:
"use client"; import { trpc } from "@/utils/trpc"; export function UserManagePage() { const [search, setSearch] = useState(""); const [role, setRole] = useState<string | undefined>(undefined); const utils = trpc.useUtils(); const usersQuery = trpc.user.list.useQuery({ search, role }); const updateRoleMutation = trpc.user.updateRole.useMutation({ onSuccess: () => utils.user.list.invalidate(), }); // 渲染逻辑... }这里有一个关键细节:useQuery的依赖参数是{ search, role },当搜索词或角色筛选变化时,tRPC 会自动重新请求并带上新参数,这个行为很像 React Query 的queryKey,但不需要手动维护 key。变更角色成功后,调用invalidate让列表自动刷新。
这些代码看起来不多,但每一步都包含类型推导,比如usersQuery.data在我写data.map(u => u.name)时,编辑器会强制检查name字段是否存在。比传统 REST 方案的体验好出几个量级。
3.3 部署上线时踩过的性能和配置坑
项目开发完,部署上线前,有几个地方值得专门做调优,我把自己实测过的经验列出来。
第一个是 Next.js 缓存策略。很多页面数据变化频率不高,可以开启静态渲染或增量静态生成,减少服务端压力。比如用户列表页面,如果你要求数据实时性不需要秒级,可以用revalidate或者直接在服务端组件里控制缓存。我的做法是:高实时性的请求(比如个人资料页)走 tRPC 客户端请求,不缓存;低实时性的页面(比如公告列表)直接静态生成,效率很高。
第二个是图片优化。Next.js 的next/image组件自带响应式和 WebP 转换,但默认配置下,远程图片需要在next.config.ts里配置remotePatterns白名单。不配置的话,开发时可能没感觉,构建部署后会一直报域名不合法。这个坑一半以上的团队都会遇到。
第三个是环境变量管理。t3code 项目里 Prisma 和 NextAuth 相关的DATABASE_URL、NEXTAUTH_SECRET等变量,一定要区分环境。我习惯在.env.local放本地开发,.env.production放生产,且.gitignore里排除这些文件。线上环境用平台的加密环境变量功能注入,不进代码仓库。
我遇到过一次比较棘手的问题:升级 Prisma 版本后,db push检测到 schema 结构变更,但因为是纯本地数据库,迁移历史文件路径不对导致反复报错。后来我用prisma migrate reset重置了本地数据库结构,再重新建表,问题解决。这也提醒我:migrate dev和db push的分工要清楚,前者用于生成迁移历史,后者用于快速同步原型,不要混用。
4. 常见问题与排查技巧实录
4.1 开发阶段最容易遇到的几个报错
t3code 从搭建到跑通,我整理了一份高频报错速查,按出现概率排序:
| 报错现象 | 可能原因 | 解决办法 |
|---|---|---|
Unable to determine the output type | 路由处理器缺少outputschema,或返回类型在运行时无法推导 | 给 procedure 显式添加outputschema |
No "ctx" found | tRPC context 没有正确传递 | 在createTRPCContext里返回对象中包含db、session等字段 |
Argument of type ... is not assignable | 前端调用时 input 类型和服务端 schema 不一致 | 检查 zod schema 字段名和前端传参是否完全一致 |
Relation "xxx" not found on model | Prisma 模型里没有声明关联关系,或字段名大小写不一致 | 检查 schema.prisma 里的relation定义 |
NEXT_PUBLIC_前缀变量未定义 | 客户端组件里读取了服务端环境变量 | 需要在.env里单独定义NEXT_PUBLIC_开头的变量 |
Hydration failed服务端渲染报错 | 服务端组件里渲染了带随机值的客户端组件 | 为内容使用稳定的 key,或用suppressHydrationWarning处理少数场景 |
其中第一个报错容易误导人,因为它提示是类型问题,但实际上是因为 tRPC 无法推断返回值的运行时结构。加上output之后,不仅报错消失,前端类型也更精确。
第二个报错在多人协作时最容易出现:A 同学在某路由里访问ctx.db,B 同学在另一路由里访问ctx.session,但 context 构造字段缺失,运行时抛错。我的经验是把 context 做成一个全局统一类型,新写路由时从公共类型里取字段,就很少出现这种问题。
4.2 类型推导失效时的排查思路
如果你发现前端调用 tRPC 方法时,类型提示忽然变宽泛了,比如data变成了any或unknown,大概率是类型链路断了一处。我的排查思路是很直接的三步检查:
- 检查编译器是否真的把服务端路由类型导出到了客户端侧。在 tRPC 的根 router 文件里,
export type AppRouter = typeof appRouter;这行是必须的,少了它,客户端拿不到完整的路由类型。 - 检查客户端 tRPC 实例化是否使用了同一个
AppRouter类型。通常createTRPCReact<AppRouter>()这一步不能省略。 - 检查 TS 配置文件是否在某些目录下开启了
skipLibCheck且有误用。如果types路径配置有问题,服务端类型可能被跳过。
实际中最隐蔽的一次问题是:我在package.json里配置了paths,把@server/*指向了服务端目录,但 tRPC 根路由里有一行import "server-only"的副作用引入,编译时把这个模块的声明错误吞掉了,导致类型链路静默变成any。排查了很久才发现是依赖的一个小包没有类型声明,在tsconfig里加一行declare module白名单解决了。这种问题没有万能解法,但类型链路断档时优先检查声明文件和环境变量路径,可以少走很多弯路。
4.3 从开发上线到团队协作的避坑清单
最后分享几个团队协作层面很容易踩坑的点,这部分经验比较零散,但每条都是从项目里真金白银换来的。
- 依赖升级要有节奏。t3code 全家桶里 Next.js 的 App Router 演进非常快,tRPC 本身也在不断调整 API 形态。我的建议是:业务高峰期尽量锁死依赖版本,宁可少升级,也别在发布前两天升 Next.js 大版本。
- 多人开发时,Prisma schema 的变更要尽量走迁移文件而不是
db push。db push适合个人开发快速迭代,但多人协作时每个人本地数据库结构很容易不一致。用prisma migrate dev生成迁移,提交到代码仓库,其他人拉到最新代码后执行prisma migrate deploy,能极大减少“我这正常你那报错”的情况。 - 接口变更要主动通知消费方。虽然 tRPC 类型系统会自动暴露变更,但有些字段是语义变了而类型没变,比如
role字段增加一个枚举值时,后端可以正常返回,前端如果用了老的枚举值做判断,逻辑上就落后了。我在团队里定了一条规矩:任何 schema 变更都要在代码评审里明确说明影响面,不能只看类型是否报错。 - 监控和日志要为 tRPC 请求打标识。我一度忽视了这个问题,线上出问题时只能看到 HTTP 200,但不知道具体是哪个 procedure。后来在 tRPC 中间件里为每个请求带上
procedurePath和requestId,日志直接结构化输出,排查效率提高了好几个档次。
5. 性能优化与可观测性补强
5.1 前端性能体检与调优实测
前面提到我把首屏 JS 体积从 248KB 压到 96KB,这里说一下具体怎么做的。首先我用@next/bundle-analyzer分析了构建产物,发现最大的体积来自一个第三方 UI 组件库的全量样式。当时其实只需要它的表格组件,但默认按全量引入,把几百个组件的样式全打进去了。排除方法很简单,换成按模块引用,构建产物体积立刻降下来。
第二个动作是精简客户端组件。比如用户列表页面,我把“列表首屏加载”改成服务端渲染,客户端只保留搜索输入框和筛选下拉的交互逻辑。这样首屏返回的是完整的 HTML 表格,而不再是一个 loading 占位加后续请求,用户感知速度明显变快。
第三个动作是缓存公共数据。比如应用顶部的用户头像下拉菜单,里面需要显示用户昵称、角色、头像,这个信息在大多数页面都一样。我在客户端用staleTime把 this query 标记为 5 分钟内不重新请求,避免每次页面跳转都带一次详情请求。代码上是这样的:
const meQuery = trpc.user.me.useQuery(undefined, { staleTime: 5 * 60 * 1000, });需要注意的是staleTime不等于“数据永远不变”,它只是让 React Query 在指定时间内不触发后台重取。用户的角色变动如果发生在服务端,客户端最多在 5 分钟后感知到。对于敏感的权限判断,我还依赖 tRPC 服务端的中间件做实时校验,这个逻辑不能依赖前端缓存,这点要牢记。
5.2 服务端性能与资源消耗的观察
t3code 项目服务端主要跑在 Next.js 的 Node.js 运行时上。我观察到一个容易被忽视的现象:很多开发者以为 t3code 是 BFF(Backend for Frontend)架构,就把所有业务逻辑都往 Next.js 里塞,结果 Next.js 服务变得越来越重,内存和 CPU 消耗上去了,性能反而比传统前后端分离更差。
我的建议是保持“薄服务端”原则:Next.js 的 API 层只做三件事——校验入参、调用业务服务、返回结果;核心的复杂计算和大量数据读写,抽到独立的业务模块或者独立的 Worker 里。tRPC 的 router 可以是薄薄的调度层,而不是把上千行业务逻辑堆在一个文件里。这样不但更易维护,部署时还可以配合 Node.js 集群模式做多实例扩展。
在 Serverless 部署环境下,还要注意一个点:Prisma 长连接在冷启动时可能报超时。Serverless 函数每次冷启动都要重新初始化 Prisma 客户端,建立数据库连接需要时间。解法是把 Prisma 客户端实例做成全局单例,并且在连接池上做合理的超时设置。我在 Vercel 上部署的项目,通过全局单例加连接超时配置,把冷启动失败率从 3% 降到了接近 0。
5.3 可观测性和日志治理的补强方案
一个合格的 t3code 项目上线前,我强烈建议把可观测性这套东西配好,否则出了问题只能靠“打开线上控制台看日志”这种原始手段。我自己的组合方案是:日志用结构化 JSON 输出,指标用 OpenTelemetry 接入,链路追踪则通过 tRPC 中间件标记每个 procedure 的耗时。
技术上可以做得很轻量:在 tRPC 路由入口挂一个 middleware,统一打印请求参数(注意脱敏)、返回状态和耗时。关键逻辑片段加console.log时,带上requestId作为关联 id,排查问题时直接按requestId过滤整条链路的日志。这样你在线上排查时不再需要“猜”,而是按时间线串联各个服务层面的调用记录。
对于告警层面,我建议给两类核心指标配置告警:tRPC 的错误率超过阈值、数据库查询的 P95 延迟超过预期值。这两类指标直接反映了用户体验的下限。配置方式不复杂,用任意的开源监控方案都能做到,核心是你得先把中间件指标暴露出来。
6. 我的经验和心得
回想这段时间用 t3code 的经历,我最大的体会是:它的价值不在于某一个框架多厉害,而在于把类型安全、开发体验、部署链路这些原本分散的点,粘合成了一条完整的主线。
在项目初期,我一度担心这套技术栈会不会太激进,等踩过一些坑、把团队规范建立起来之后,反而觉得这种激进的约束是好事——它逼着你把类型契约和接口边界梳理清楚,规避了大量线上才能发现的问题。
最后分享一个小技巧:如果你准备在团队里推 t3code,别一上来就铺全量迁移,先选一个边界清晰的内部工具页面做试点,让团队体验一下“改字段立刻全局报错”的爽感,自然就会有人愿意继续用。技术选型最终是人心的认同,这句话在团队协作里比任何架构图都重要。