☰
t3code代码生成:基于Schema的类型驱动CRUD开发实践
2026/10/28 6:27:07 网站建设 项目流程

t3code 这个名字,最早是我给自己内部工程化脚手架起的代号。项目做久了你会发现,最折磨人的往往不是业务逻辑本身,而是 CRUD 页面里的重复劳动:后端把某个字段类型改一下,前端接口层、类型定义、表单校验、表格列要跟着改四个文件,漏改一个,联调时才炸。如果你做过全栈或中后台项目,应该秒懂这种痛。t3code 不是某个开源仓库,也不是业界标准名词,它是我在实践中沉淀的一套三段式代码生成思路——Template(模板)、Type(类型)、Transform(转换)。

这篇文章会从项目动机讲起,把整体设计和核心实现一步步拆开,最后附上我踩坑后整理的速查表。内容偏实操,适合正在做中后台、全栈脚手架、低代码平台或代码生成工具的朋友参考。我会尽量说人话,涉及代码的地方直接贴可运行的示例,你可以边读边对照自己的项目思考。

1. 项目背景:三代演进与 t3code 的定位

1.1 三代代码演进,t3code 站在哪一代

先聊聊团队代码组织方式的变化,这有助于理解 t3code 为什么长这样。

第一代是整页复制。新页面来了,找个长得像的老页面,全文件复制过去改改字段名。快是真快,但副作用也明显:老页面里积攒了三年的历史包袱、废弃逻辑、临时补丁,会被你原封不动带到新项目里。代码越复制越臃肿,最后谁都不敢动那些“老大哥”文件。

第二代是组件化抽象。把按钮、表格、弹窗、表单拆成公共组件,用 props 配置控制差异。这一步能消灭大量视觉层面的重复,但解决不了更深的问题:组件只关心 UI,不关心数据从哪来、字段是什么、校验规则怎么变。于是你仍然要手写一堆接口定义、TS 类型、表单校验逻辑、列配置。

t3code 属于第三代思路:类型驱动的代码生成。简单说,把业务实体的字段、类型、默认值、校验规则集中写在一份 schema 里,然后让生成器根据这份 schema 批量产出接口层、类型定义、表单配置、表格列配置这些样板代码。我常用一个类比:一代是复印整本书,二代是提供标准零件,三代是画一张 CAD 图纸让机床自动加工。这份图纸就是 schema 契约。

1.2 三个痛点:接口不同步、样板代码、多端漂移

为什么我要专门做 t3code,而不是继续手写或者依赖现成的 low-code 平台?核心是三个痛点在叠加。

第一个痛点是接口与类型不同步。后端 DTO 加了一个字段或者改了枚举值,前端这边可能还停留在旧类型。运气好,编译期报错能兜住一部分;运气不好,页面运行到某个分支才出现 undefined,用户比你先发现问题。类型一旦分散在多个文件里,谁也不敢保证每次后端变更都能同步修改到位。

第二个痛点是样板代码重复。一个 30 字段左右的实体,要盖出 TS 类型、请求函数、表单默认值、校验规则、表格列配置,手写的行数加起来差不多 600 到 800 行。这些代码本身没有业务难度,纯属体力活。团队成员每天花大量时间在这种复制粘贴上,真正的业务逻辑反倒没有精力深入思考。

第三个痛点是多端漂移。同一个用户实体,管理端要一套列表字段展示,移动端要一套简化结构,数据平台要一套统计口径。如果每个端各写各的类型和接口层,字段口径会慢慢漂移,同样是“状态”,有的是数字 0/1,有的是字符串 active/disabled,到最后没人说得清哪个才是事实源。

1.3 适用场景与边界

我实际用下来,t3code 这类方案最适合的是字段驱动的中后台系统:实体结构相对稳定,页面以表单和表格为主要交互形态,需要多端复用同一套数据契约。用户管理、订单管理、商品管理、配置管理这类模块是典型的受益场景。

但它不是万能的。如果你的场景是复杂业务流程(审批流、规则引擎、可视化编排),或者还在快速探索阶段,实体字段十天变三次,那就不适合引入生成器。前期实体不稳定时,写死的代码反而更容易调整;等结构稳定后再把重复部分交给生成器,心智负担会小很多。另一个边界是:生成器适合生成大家约定俗成的结构,不适合生成业务判断。别指望它帮你做“哪些用户能看到哪些订单”这种策略逻辑。

2. 整体设计:Template、Type、Transform 三层协作

2.1 T1 模板层:先定“人样”,再谈生成

t3code 的第一层是模板。在设计生成器时,我坚持一个原则:先确定生成结果长什么样,再考虑怎么解析。很多人做脚手架喜欢先写解析逻辑,最后才拍脑袋定输出格式,生成出来的代码又丑又难维护,团队成员一眼就不想用。

模板层要做三件事。第一,把产出物拆成独立模板文件:类型模板、API 模板、表单模板、表格模板各归各,互不掺和。第二,模板内只做展示判断,不做业务计算。第三,模板的语言选型要贴合团队习惯。我们团队用的是 Handlebars 写 .hbs 模板,简单、易读、学习成本低;如果你要生成的是复杂嵌套结构,也可以考虑 EJS 或者直接用 TS 函数拼接。

模板层还有一个容易忽略的点:代码风格要跟团队 lint 规则一致。模板里如果满是反引号拼接字符串,生成出来的文件大概率过不了 ESLint,队友拿到手里第一件事就是手动格式化。所以我把 prettier 和 eslint --fix 接在生成流程的最后一步,保证产物落地即规范。

2.2 T2 类型层:schema 当唯一事实源

T2 的类型层是整个方案的灵魂。t3code 里的“类型”不只是 TS 的 type,而是广义的数据契约:字段名、字段类型、是否必填、默认值、枚举值、校验规则、展示元信息,全部收敛到一份 schema 里。

选型上我用了 zod。原因很简单:zod 既能做运行时校验,又能通过 z.infer 直接推断出静态类型,一份代码两种用途。更重要的是,zod 的 schema 可以放进一个共享包(比如 packages/shared),前端项目和后端项目都引用同一份契约。后端改了 schema,前端生成器重新跑一遍,字段就同步了,不再需要靠人肉去对齐。

实际工作中,我还给 schema 加了一些自定义 meta 信息,用 zod 的 description 字段承载。比如某个字段的表格标题是“创建时间”,表单组件要渲染成日期选择器,这些信息不属于数据校验本身,但生成代码时需要它。我在 schema 里约定用 @label、@widget、@table 这样的注解来标记,解析的时候再提取。

2.3 T3 转换层:解析 schema 并渲染模板

T3 转换层负责把 schema 变成模板上下文,再渲染成文件。我们实现的流程是:读取 schema 文件 → 转成 JSON Schema → 提取字段元信息 → 组装上下文对象 → 用 Handlebars 渲染 → 写入目标目录。

这里有个细节:不直接手写 AST 解析,而是先把 zod schema 转成标准 JSON Schema,再遍历 properties。好处是结构稳定、可控,而且理论上以后如果想接入其他 schema 来源(比如后端 OpenAPI),只需要替换转换这一步,模板层完全不用动。

生成器脚本我用tsx直接跑 TS 文件,避免“先编译再生成”的鸡生蛋问题。运行时只需要 Node 18+ 和一个 pnpm 管理好的 monorepo。输出产物是普通的 .ts/.tsx 文件,团队成员可以读、可以改、可以 review,不是黑盒。

2.4 关键取舍:为什么选 schema 而不是 JSON Schema / ORM 实体

每次跟朋友聊这个设计,都会被问:为什么不直接用 JSON Schema,或者干脆用 ORM 实体作为生成源?我分别说下理由。

JSON Schema 本身是跨语言的行业标准,但它有个痛点:写起来太啰嗦。同样的一个必填字符串字段,JSON Schema 要写 type、minLength、description、required 数组全部声明,而 zod 一行z.string().min(2).describe("@label 用户名")就搞定了。维护成本差别很大。另外 JSON Schema 没有原生默认值语法(虽然可以用 default 字段,但约束不够直观),zod 的.default()语义清晰得多。

ORM 实体(比如 TypeORM、Prisma 的 model)也不适合当生成源。ORM 描述的是数据库存储模型,包含了主键策略、索引、关联关系这些持久化语义,而前端页面需要的只是接口层的输入输出契约。比如数据库里用户密码字段一定存在,但接口层永远不会返回给前端;ORM 实体会把这些无关字段也带进生成产物,反而污染了前端代码。打个比方:ORM 是仓库的账本,schema 是发货清单,前端需要的是发货清单,不是仓库账本。

3. 从零搭建 t3code:目录、Schema 与生成器脚本

3.1 准备 monorepo 与运行环境

先把工程结构搭起来。我用的是 pnpm workspace 管理的 monorepo,目录大致长这样:

t3code-project/ ├── packages/ │ └── shared/ │ └── src/ │ └── schemas/ │ ├── user.schema.ts │ └── order.schema.ts ├── apps/ │ └── web/ │ ├── src/ │ │ ├── api/ │ │ ├── types/ │ │ ├── form/ │ │ └── table/ ├── scripts/ │ ├── generator/ │ │ ├── index.ts │ │ ├── context.ts │ │ └── templates/ │ │ ├── api.hbs │ │ ├── type.hbs │ │ ├── form.hbs │ │ └── table.hbs ├── package.json └── pnpm-workspace.yaml

运行环境只需要 Node 18+。脚本层面我装了四个关键依赖:zod、zod-to-json-schema、handlebars、tsx。前三个负责 schema 解析和模板渲染,tsx 负责让 Node 直接运行 TS 脚本。

提示:如果你还在用 ts-node,遇到 ESM 项目大概率要折腾一堆 loader 配置。tsx 在这方面省心很多,npx tsx scripts/generator/index.ts直接跑,不用改 tsconfig,也不用区分 module 类型。

3.2 定义第一份 schema 的完整示例

这是整个方案的起点。我拿用户管理模块举例,一个典型的 user schema 长这样:

// packages/shared/src/schemas/user.schema.ts import { z } from "zod"; export const UserSchema = z.object({ id: z.string().uuid().optional().describe("@label ID @table false"), name: z.string().min(2).max(20).describe("@label 用户名 @table true"), email: z.string().email().describe("@label 邮箱 @table true"), role: z .enum(["admin", "editor", "viewer"]) .default("viewer") .describe("@label 角色 @table true @widget select"), status: z .enum(["active", "disabled"]) .default("active") .describe("@label 状态 @table true @widget select"), createdAt: z .string() .datetime() .optional() .describe("@label 创建时间 @table true @widget date"), }); export type User = z.infer<typeof UserSchema>;

仔细看这段代码,我通过.describe()塞进了展示层的注解。@label 是表格列标题或表单标签;@table false 表示列表页不展示该字段;@widget select 表示表单里渲染成下拉框。这样一份 schema 同时承担了校验、类型、展示三层信息。

命名上我也定了统一规范:schema 变量用XxxSchema,导出的类型用Xxx。因为生成器会依据文件名推导实体名,如果团队里有人写userSchem或者UserSchema大小写混乱,后面生成的文件名就会很难看。约定优于配置,这种细节越早统一越好。

3.3 生成器核心实现

核心脚本的流程不复杂,关键的代码长这样:

// scripts/generator/index.ts import { readdirSync, readFileSync, writeFileSync, mkdirSync } from "node:fs"; import { join, basename } from "node:path"; import { toJSONSchema } from "zod-to-json-schema"; import Handlebars from "handlebars"; import { fileURLToPath } from "node:url"; import { UserSchema } from "../../packages/shared/src/schemas/user.schema.js"; const schemaDir = "packages/shared/src/schemas"; const files = readdirSync(schemaDir).filter((f) => f.endsWith(".schema.ts")); function buildContext(schema: any) { const jsonSchema = toJSONSchema(schema, { target: "jsonSchema7" }); const fields = Object.entries(jsonSchema.properties).map(([key, value]: any) => { const desc = value.description ?? ""; const label = desc.match(/@label\s+([^\s]+)/)?.[1] ?? key; const widget = desc.match(/@widget\s+([^\s]+)/)?.[1] ?? "input"; const showInTable = !/ @table false/.test(desc); return { key, label, type: value.type, widget, showInTable, required: jsonSchema.required?.includes(key) ?? false, defaultValue: value.default ?? "", isEnum: Array.isArray(value.enum), enumValues: value.enum ?? [], }; }); return { fields }; } for (const file of files) { const entityName = basename(file, ".schema.ts"); const ctx = buildContext(UserSchema); const templateDir = "scripts/generator/templates"; for (const tplFile of ["type", "api", "form", "table"]) { const tpl = Handlebars.compile(readFileSync(join(templateDir, `${tplFile}.hbs`), "utf-8")); const output = tpl({ name: entityName, ...ctx }); const targetMap: Record<string, string> = { type: `apps/web/src/types/${entityName}.ts`, api: `apps/web/src/api/${entityName}.ts`, form: `apps/web/src/form/${capitalize(entityName)}Form.tsx`, table: `apps/web/src/table/${capitalize(entityName)}Columns.tsx`, }; mkdirSync(dirname(targetMap[tplFile]), { recursive: true }); writeFileSync(targetMap[tplFile], output, "utf-8"); } }

这段代码做了一个简化:我把 UserSchema 直接 import 进来,实际项目中你应该用动态 import 遍历所有 schema 文件。因为 tsx 支持 ESM,动态import(path)是可以直接工作的。

模板文件里,以 API 模板为例:

// apps/web/src/api/{{name}}.ts import { request } from "@/lib/request"; import type { {{capitalize name}} } from "@/types/{{name}}"; export function get{{capitalize name}}(id: string) { return request.get<{{capitalize name}}>(`/{{name}}/${id}`); } export function list{{capitalize name}}() { return request.get<{{capitalize name}}[]>(`/{{name}}`); } export function create{{capitalize name}}(data: {{capitalize name}}) { return request.post(`/{{name}}`, data); } export function update{{capitalize name}}(id: string, data: Partial<{{capitalize name}}>) { return request.patch(`/{{name}}/${id}`, data); } export function delete{{capitalize name}}(id: string) { return request.delete(`/{{name}}/${id}`); }

要点在于,Handlebars 默认不支持capitalize这样的函数,需要注册 helper:

Handlebars.registerHelper("capitalize", (str: string) => str.charAt(0).toUpperCase() + str.slice(1));

这块很容易被新手忘掉,一运行就报 “capitalize is not a function”。

3.4 接入生成命令与 watch 模式

生成器脚本写好之后,要把它接成一条可重复执行的命令。我在根目录 package.json 里加了:

{ "scripts": { "gen": "tsx scripts/generator/index.ts", "gen:watch": "chokidar \"packages/shared/src/schemas/**/*.schema.ts\" -c \"pnpm gen\" --debounce 300" } }

watch 模式用 chokidar 实现,情理之中,但要注意两个细节。第一个是--debounce 300:编辑器保存文件的时候经常触发多次写入事件,没有防抖的话生成器会被连续调用三四次,白白浪费 CPU。第二个是监听路径一定要排除输出目录,否则会出现“生成代码 → 触发监听 → 再生成”的死循环。schemas 目录通常跟输出目录不重叠,但如果你把生成产物也放进 packages/shared 里,就必须手动加 ignore 规则。

关于生成文件要不要提交进 git,我的建议是提交。这样团队成员不需要先跑一遍生成器就能正常开发,CI 也少一个步骤。缺点是 schema 改动后如果忘记重新生成,提交的产物会跟 schema 不一致。所以更稳妥的做法是在 CI 加一条校验:运行pnpm gen,然后git diff --exit-code,一旦有未提交的差异就报错。这个流程成本低,但能有效防止“只有我一个人记得跑生成器”的情况出现。

3.5 生成器选型解析

动笔之前我纠结过用 plop 还是手写脚本,最后选了后者。plop 的交互式 prompt 体验很好,适合“开发者手动触发、回答几个问题再生成”的场景;但 t3code 的核心场景是全自动类型驱动,开发者改完 schema 就跑命令,不需要交互提问。这种情况下 plop 反而多了一层 prompt 配置的成本。

如果后续需要修改已有代码而不是追加新文件,可以渐进式引入 ts-morph。ts-morph 能把 TS 文件解析成 AST,然后通过代码精准地插入 import、修改类型、新增属性。比如给现有 API 函数追加一个请求参数,用字符串正则改很容易误伤,用 AST 就安全得多。我目前的策略是:生成新文件用 Handlebars 模板,更新已有文件用 ts-morph,各管一段。

另一个值得强调的选型决策是:表单初始值不从模板硬编码,而是从 zod 的.default()提取。比如 role 字段默认 viewer,生成器会把 defaultValue 填成 "viewer"。这样 schema 改了默认值,前端表单也跟着变,不会出现两边各写各的默认值最后互相对不上的问题。

4. 实战演示:十分钟生成一套用户管理模块

4.1 场景设定:一个用户实体新增两个字段

假设现在产品经理说:用户列表要加“是否认证”和“标签”两个字段。放在以前的流程里,我要改后端 DTO、数据库迁移、前端类型、接口层、表单、表格列,最少 6 个文件。用 t3code 的流程就简单了。

第一步,改 schema。我只在 user.schema.ts 里加两行:

isVerified: z.boolean().default(false).describe("@label 是否认证 @table true @widget switch"), tags: z.array(z.string()).default([]).describe("@label 标签 @table false @widget tags"),

第二步,跑pnpm gen。生成器会重新扫描 schema,刷新所有依赖这份 schema 的模板产物。整个操作耗时不超过十秒,比我以前在编辑器里全局搜索替换字段要快得多。

4.2 生成结果:类型、接口、表单、表格四层拆解

跑完生成器,打开产出文件看效果。

类型定义文件自动生成了新字段:

// apps/web/src/types/user.ts export type User = { id?: string; name: string; email: string; role: "admin" | "editor" | "viewer"; status: "active" | "disabled"; isVerified: boolean; tags: string[]; createdAt?: string; };

API 层没有变化,仍是那五个标准的 CRUD 方法。表单文件的 initialValues 自动带上了新字段的默认值:

// apps/web/src/form/UserForm.tsx export const initialValues: UserFormValues = { name: "", email: "", role: "viewer", status: "active", isVerified: false, tags: [], };

表格列配置也自动新增了“是否认证”列,并且根据 @table false 跳过了标签列:

// apps/web/src/table/UserColumns.tsx export const userColumns = buildColumns([ { key: "name", title: "用户名" }, { key: "email", title: "邮箱" }, { key: "role", title: "角色", render: role => roleText[role] }, { key: "status", title: "状态" }, { key: "isVerified", title: "是否认证", render: v => v ? "是" : "否" }, ]);

这里体现了一个重要设计:模板只管“生成”,不管“渲染”。枚举值的中文映射、状态的徽标颜色这些业务展示逻辑,生成器不碰,留给开发者手写。生成的代码是起点,不是终点。

4.3 手动微调与渲染优先级

生成器不可能覆盖所有需求。比如表格列里 status 字段希望渲染成彩色 Tag,role 字段要显示中文,这些都是生成器不知道的。我的做法是给模板留一个“渲染优先级”约定:

如果 schema 字段标注了@widget select,模板只生成一个 Select 组件和枚举选项,不生成复杂联动。如果要自定义单元格渲染,在生成的列配置函数里手动写覆盖逻辑,生成器每次运行会保留手写部分。具体实现是在列配置文件中区分两个区域:自动生成区域用注释标记// @generated,手写区域放在文件底部,模板解析时只重建标记区域之间的内容。这样每次重新生成不会把手工微调抹掉。

这个“部分生成”的能力是代码生成工具成熟度的分水岭。如果生成器每次跑完都把手工改动清空,团队成员会越来越抗拒使用它。保留手工区域,人机协作才能顺畅。

5. 踩坑记录与排查技巧

5.1 高频问题速查表

以下问题都在实际使用中遇到过,我先给速查表再补充说明。

现象根因解决办法
生成的接口文件名大小写不对,导入报 404模板里用{{name}}时没有 normalize统一用小驼峰实体名,文件名用 kebab-case 或原样小写下划线
生成代码在 IDE 里全是红色波浪线模板缩进、空格与 lint 规则冲突生成器最后接eslint --fix和prettier --write
watch 模式 CPU 飙高、反复生成chokidar 监听了输出目录监听路径排除生成目录,追加--ignore规则
表单里默认值还是旧值schema 的 default 改了但生成文件没重跑检查 generate 命令是否真的被执行;把 gen 命令接在 pre-commit 钩子里
手写代码被生成器覆盖生成文件整个重写,没有保留手工区采用// @generated标记区域的部分更新策略
加了新 schema 文件但生成器没反应遍历目录时硬编码了文件名列表改成readdirSync动态扫描,不要写死 schema 数组
枚举字段生成的 options 为空解析时没处理 JSON Schema 的 enum 属性在 buildContext 里显式提取 value.enum 并传给模板

5.2 三个独家避坑原则

第一个原则:模板不做业务。模板文件里不要出现 if businessType === "xxx" 这种逻辑。业务判断应该交给 schema 的 meta 数据和手写区代码,模板只管“根据上下文输出固定结构”。一旦模板开始承载业务规则,改起来比改业务代码还痛苦,因为你要同时改模板、改上下文、改手写区三处。

第二个原则:先落临时目录,diff 后再写盘。生成器不要直接覆写目标文件,先把全部内容写到.tmp/目录,然后逐个与现有文件做 diff,有变化才替换。这个习惯最开始看起来多余,但实际帮了我大忙。有一次我改坏了模板,生成结果少了一个字段,如果没有 diff 直接全量覆盖,手写区那些幸苦维护的代码在毫秒之间就全没了。diff 机制让我能提前发现异常,甚至可以做成交互式确认。

第三个原则:生成文件头顶加// @generated标记,并明确约定不要手动修改自动区域。这不仅是给开发者看的警告,更是给代码工具看的“免死金牌”。后续无论是 eslint 配置、prettier ignore 还是 Code Review 规则,都能基于这个标记做针对性处理。

5.3 这几个问题背后的原理

循环触发问题值得多说两句。chokidar 监听的是 schema 目录,理论上不应触发输出目录事件,但我曾遇到过生成器在输出目录里创建文件后,又反向触发 schema 读取的边界场景——因为生成器启动时会读取 schema 文件,如果文件访问时间戳被某些 IDE 当作“文件变更事件”推送出去,监听器就会误判。解决方式很粗暴:监听器配置里加awaitWriteFinish: { stabilityThreshold: 500 },让文件在停止写入 500ms 后才视为稳定,再触发命令。

lint 报错问题则暴露了模板缩进的坑。Handlebars 的{{#each}}块在模板文件里会携带自己的缩进,生成出来经常是“多两个空格”或“空行不对”。我的做法是在渲染完成后统一交给 prettier 收尾,而不是靠手调模板缩进去凑。别浪费时间手动对齐模板缩进,让格式化工具干活。

还有一个坑是我的大意:动态 import schema 时用了相对路径../../packages/shared/src/schemas/user.schema.js,在 tsx 环境下.js后缀没问题,但如果你切换到普通 node 跑编译后的 dist,就会报模块找不到。这类问题通常在搬服务器或者换 CI 环境时爆发,建议在生成器脚本里用路径别名统一管理,不要到处手写 ../。

6. 什么时候不要用 t3code,以及后续扩展思路

6.1 使用边界再谈

文章开头提过边界,这里再强调一次。如果你的项目还在三天两头改数据库表结构,生成器大概率是负担而不是工具。schema 本身也是代码,也要维护,也要写.describe()注解;改动频繁时,维护 schema 的成本跟手写业务代码差不了多少。

另外,生成器并不是代码 Review 的替代品。自动生成的类型定义和接口层可以减轻体力劳动,但字段是否应该暴露给前端、接口鉴权是否合理、表单校验规则是否符合产品预期,这些判断仍然需要人来把关。我见过一些团队把代码生成当成“甩锅工具”,由于是生成器产出的代码所以不 Review,出问题就怪生成器,这其实是对工具的错误依赖。

还有一点:如果团队里只有两三个人,而且每个人都对全部代码了如指掌,那手写可能比引入生成器更快。t3code 的价值在大团队和多端场景里体现得最明显——当信息开始传递不畅,一份 schema 契约比口头沟通更可靠。

6.2 扩展方向

目前 t3code 只做了前端层的生成,我后续计划顺着契约往上下游扩展三个方向。

第一个方向是生成 mock 服务。既然 schema 里已经定义了字段类型和默认值,完全可以推导出 mock 数据的生成规则,再配合 MSW 或 json-server 直接在本地起一套假接口。前端开发不需要等后端联调,这对并行开发效率的提升非常明显。

第二个方向是对接 OpenAPI。现实项目里如果后端接口已经由 OpenAPI 描述,可以把解析器从 zod-to-json-schema 替换成 openapi-typescript,让 schema 事实源从“前端共享包”变为“后端接口文档”。生成器主体逻辑不用动,换掉转换层就行。这也是我当初坚持把解析跟模板分离的原因。

第三个方向是 AI 辅助模板起草。生成器的模板维护成本是客观存在的,每当团队决定引入一种新的页面形态,就要写一套新模板。现在我会让 AI 根据既有的模板风格直接起草新模板,我再负责 review 边界情况。模板本身是纯文本,非常容易做这种辅助生成。

6.3 我个人用下来的几点体会

如果只留一条经验,我会说:真正让 t3code 受益的,不是生成器写得有多巧妙,而是那份所有人都愿意遵守的 schema 契约。它把“约定”从口头搬进了代码,从文档搬进了自动化的流程。团队里新同学看代码,不用到处问“这个字段哪来的”,看一眼 schema 就清楚全貌。

另一个体会是生成代码不要太贪心。我最早的版本试图把表单联动、权限校验、复杂渲染全部生成出来,最后发现这些自动化生成的复杂逻辑比手写还难调试。后来学乖了:凡是需要人判断的规则,一律留给手写区;生成器只负责 80% 的样板注入,剩下的 20% 智能空间由人接管。你能生成的复杂度越高,技术债反而越重。

最后分享一个小技巧:每次改完 schema 跑生成器后,顺手打开生成的 diff 看一眼。不要只关心命令是否报错,要看字段是否出现在预期文件里。这花不了二十秒,但能让你及时发现模板上下文解析的偏差。工具再好用,最终判断者还是人。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询