TypeScript + JSDoc 是渐进式产线最务实的类型安全路径:checkJs 模式支持在 .js 中用 @type 精准标注类型,配合 ESLint 强制参数/返回值类型、VS Code 实时校验与跳转,且需同步维护 types/api.d.ts 以保障真实安全。
直接上结论:TypeScript + JSDoc 不是“替代方案”,而是生产环境里最务实的类型安全落地路径——尤其当你无法全量迁入 .ts,又必须守住关键链路不崩时。
checkJs + @type 比全写 .ts 更适合渐进式产线很多团队卡在“要不要重写所有 .js 文件”这个决策点上,其实没必要。TypeScript 的 checkJs 模式允许你在不改后缀、不重构调用链的前提下,对 JS 文件做精准类型校验。
checkJs: true 启用后,VS Code 和 tsc 都会把 .js 当作带类型的源码处理,但只报错、不编译输出/** @type {import('./types').ApiResponse} */ 这类显式标注,能覆盖 API 响应、表单序列化、第三方 SDK 回调等 JS 里最难推断的动态结构fetch().then(res => res.json()) 这种链式调用,纯靠推断根本拿不到 res 的 shape,必须人工锚定types/api.d.ts 写一次,多个 .js 文件通过 @type 引入,文档和校验天然同步@param 和 @returns 必须带类型,否则 ESLint 会报错光写注释不写类型,等于没写。ESLint 插件 eslint-plugin-jsdoc 的 require-param-type 和 require-returns-type 规则不是摆设,它强制你补全类型信息,否则 CI 直接失败。
/** @param name 用户名 */ → 缺少 {string}
/** @param {string} name 用户名 */ 或更严谨的 /** @param {string | undefined} name 用户名 */
/** @returns {Promise<ProductListResponse>} */,注意用 < 和 > 转义尖括号Promise<Array<{id: string, price: number}>> 这类也能被 tsc 和 IDE 正确识别后端字段变更时,JS 里最容易因拼写错误导致运行时 undefined,而 JSDoc 标注配合类型定义能提前暴露问题。
types/api.d.ts 中定义响应结构,比如 export interface ProductListResponse { products: Array<{ handle: string; title: string }> }
/** @type {import('./types').ProductListResponse} */ 标注变量,不要手写内联类型(易错且难维护)js/ts.implicitProjectConfig.checkJs: true 的 VS Code 设置,编辑器立刻能跳转到接口定义、悬停看字段、输入 product. 就提示 handle 和 title
product_handle,但你定义成 handle,tsc 会报错“Property 'handle' does not exist on type ...”,而不是静默忽略不会自动识别。apidoc 默认只解析 @api 系列标签,@param {string} foo 这类 JSDoc 类型语法它当普通文本处理。要让文档和类型真正联动,得靠两层机制:
apidoc 的 typescript.enabled: true 配置,让它读取 tsconfig.json 和类型定义文件,再结合 @apiParam 手动映射字段与类型(例如 @apiParam {String} [name] 用户姓名)types/api.d.ts 里的字段名、必选性、嵌套层级,严格对齐到 @apiParam 和 @apiSuccess 的描述中;CI 可加脚本比对二者差异@type 和接口定义,文档更新 = 类型变更 = 代码变更,三者锁死最常被忽略的是类型定义文件的维护节奏——一旦后端加了个新字段,types/api.d.ts 必须同步,否则所有 @type 标注都变成“假安全”。这步没人盯,整个链路就断了。