Codegen在前端工程化中的实战应用与常见问题解析

A. 子谦 框架 阅读 1,276
赞 14 收藏
二维码
手机扫码查看
反馈

codegen 生成的 TypeScript 类型里,optional 字段全没了?

今天上线前跑了一遍 codegen,结果发现接口返回的 user?.profile?.avatarUrl 在 TS 里直接报错:Property 'avatarUrl' does not exist on type '{ name: string; }'。我盯着控制台愣了三秒——这字段明明是后端文档里标了 optional 的啊!而且上个月还好好儿的。

Codegen在前端工程化中的实战应用与常见问题解析

第一反应是“是不是后端改 OpenAPI spec 没加 nullable: true"x-nullable": true”?但看了下最新的 openapi.json,字段定义是这样的:

"avatarUrl": {
  "type": "string",
  "description": "头像地址,可能为空"
}

没写 nullable,也没加 "x-nullable",但之前 codegen 是认这个为可选的……说明不是 spec 问题,是工具行为变了。

我立刻切到本地跑 npx @openapitools/openapi-generator-cli generate -i openapi.json -g typescript-axios --additional-properties=typescriptThreePlus=true,supportsES6=true,生成完一瞅——果然,所有没显式标 "nullable": true 的字段,都变成了必填项,连 ?. 都不给你留。

这里我踩了个坑:以为 typescript-axios generator 默认会把没标 required 的字段当 optional 处理(毕竟 OpenAPI 规范里,required 数组之外的字段就是 optional)。但查了下 generator 的源码和 issue,发现它从 v6.6 开始默认启用了 strictNullable 行为(哪怕你没配),也就是说:只有显式写了 "nullable": true 或字段在 required 数组里缺失 + 类型本身允许 null(比如 "type": ["string", "null"]),才会生成 string | null;否则一律按非空类型处理。

折腾了半天发现,这不是 bug,是 feature —— 它现在更“严格”了。但问题是:我们整个后端团队压根没在 spec 里补 nullable,因为以前不用补也 work。现在要 retroactively 改几百个字段?不现实。

后来试了下发现,其实有个参数能关掉这个行为:nullable(注意不是 strictNullable)。

官方文档里写得特别隐晦,在 typescript-axios 模板 README 最底下有一行小字:nullable: Generate nullable types (default: false)。但它没说清楚——这个 false 其实是指“不生成 | null”,而不是“不处理 nullable”。关键是:当设为 true 时,它会把所有没出现在 required 数组里的字段,自动加上 | null(前提是字段类型本身支持 null,比如 string → string | null)。

但等等……我们想要的是 avatarUrl?: string(可选属性),不是 avatarUrl: string | null(必填但可为 null)。这两者语义差挺大:undefinednull 在 JS 里是两回事,尤其用 ?. ?? 的时候。

所以真正要的,其实是让 generator 把非 required 字段生成成 ? 修饰的可选属性。翻了半天 generator 的 issue,终于找到一个靠谱方案:用 modelPropertyNaming=original + 自定义 mustache 模板,太重了,pass。

最后试了下 typescript-fetch generator —— 它默认就按 OpenAPI 原意来:不在 required 里的字段,直接生成 fieldName?: type。但项目里已经重度依赖 typescript-axios 的 response interceptor 和 custom instance 封装,换 generator 成本太高。

折中方案来了:不改 spec,不换 generator,只加一个配置开关:useOptionalProperties=true

这个参数在 typescript-axios v6.6+ 是默认 false 的,但一旦打开,它就会把所有非 required 字段生成为可选属性(?:),而不是强制要求你写 nullable。完美匹配我们现在的 spec 状态。

最终命令改成这样:

npx @openapitools/openapi-generator-cli generate 
  -i openapi.json 
  -g typescript-axios 
  --additional-properties=typescriptThreePlus=true,supportsES6=true,useOptionalProperties=true

跑完再看生成的 model:

export interface UserProfile {
  name: string;
  avatarUrl?: string; // ✅ 这次有了 ?
  bio?: string;
  lastLoginAt?: string;
}

再试 user.profile?.avatarUrl,TS 不报错了,运行时也正常兜底。搞定。

顺手还加了个小 patch:在 api.ts 里给 axios response interceptor 加了层浅转换,把 null 字段自动转成 undefined,避免后端偶尔塞个 "avatarUrl": null 导致 ?. 失效:

axios.interceptors.response.use(response => {
  const cleanNulls = (obj: any): any => {
    if (obj === null) return undefined;
    if (typeof obj === 'object' && obj !== null) {
      Object.keys(obj).forEach(key => {
        if (obj[key] === null) {
          obj[key] = undefined;
        } else {
          cleanNulls(obj[key]);
        }
      });
    }
    return obj;
  };
  response.data = cleanNulls(response.data);
  return response;
});

(当然,这步不是必须的,但加了心里踏实点。毕竟后端有时候会偷偷塞 null 进来,而我们的 TS 类型现在是 ?: string,不是 : string | null

还有一个小尾巴:生成的 enum 类型里,如果某个 value 是 null,它还是会生成 NULL = "null",但实际 JSON 里是 null 字面量,导致反序列化失败。不过我们项目里目前没用到 enum + null 的组合,先忽略。如果哪天碰到了,再单独 patch template。

总结一下踩坑点:

  • OpenAPI Generator v6.6+ 默认开启 strictNullable,但没改默认行为提示,容易误判
  • nullable=true 是用来开 | null 的,不是开 ? 的;真正控制可选字段的是 useOptionalProperties=true
  • 别信文档里“默认值”那句话,一定要自己跑一遍生成结果验证
  • 本地加个 make gen 脚本,比每次手敲命令靠谱多了

以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。比如你怎么处理后端返回 null 但前端想当 undefined 的场景?或者你们团队是怎么推动后端补 nullable 字段的?求分享。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论