VS Code中带递归类型的JSDoc智能提示(IntelliSense)失效问题求解
我最近遇到了个VS Code智能提示的头疼问题,折腾了半天没完全解决,想请教大家怎么破:
场景是这样的:
- 第一种情况:当我定义的对象只有单个非递归键(比如
bar)时,VS Code的IntelliSense能正常识别,输入对象变量后能自动弹出bar的提示; - 第二种情况:一旦给对象加了一个递归的键(比如
foo,它指向自身的异步函数调用),整个对象的智能提示直接“罢工”了——不管是bar还是foo,输入变量后完全没有任何键提示。
我自己试着写了一段代码想绕开这个问题,但智能提示还是没恢复,代码如下:
const defer = () => { let res, rej; const p = new Promise((_res, _rej) => { res = _res; rej = _rej; }); return { p, res, rej }; } let _p = null; /** * @typedef { <T>T } Custom */ /** * @type { <T>(f:T)=>(()=>Promise<Custom<ReturnType<T>>>) } */ const h = (fn) => { return async () => { if (_p) { return _p; } const { p, res, rej } = defer(); _p = p; res(await fn()) return p; } }; const a = h(() => { return { bar: 2, foo: a() }; }); // Testing (async () => { const k = (await a()); console.log(k);// <ref *1> { bar: 2, foo: Promise { [Circular *1] } } })();
这段代码运行逻辑是正常的,能输出带循环引用的对象,但就是VS Code的智能提示完全不工作——输入k.之后什么反应都没有。
后来我琢磨出问题出在JSDoc的类型定义上:VS Code的IntelliSense基于TypeScript,而TypeScript处理递归/循环类型时,必须要有明确的类型别名锚点,靠模糊的泛型推断根本搞不定循环引用。
下面是我调整后的可行方案,亲测智能提示能正常工作:
步骤1:明确声明递归类型别名
先用@typedef定义一个清晰的递归对象类型,直接告诉TypeScript这个结构里包含自身的Promise引用:
/** * @typedef {Object} RecursiveObject * @property {number} bar - 普通数字属性 * @property {Promise<RecursiveObject>} foo - 递归的Promise属性,指向自身类型 */
步骤2:调整工具函数h的JSDoc类型
让h函数的返回类型明确关联这个递归类型,避免模糊的泛型推断:
let _p = null; /** * @template T * @param {() => T} fn - 返回目标结构的函数 * @returns {() => Promise<T extends object ? RecursiveObject : T>} - 返回生成递归对象的异步函数 */ const h = (fn) => { return async () => { if (_p) { return _p; } const { p, res, rej } = defer(); _p = p; res(await fn()) return p; } };
步骤3:明确标注a的类型
给a加上明确的类型注解,让IntelliSense直接知道它返回的是我们定义的递归类型:
/** @type {() => Promise<RecursiveObject>} */ const a = h(() => { return { bar: 2, foo: a() }; });
测试效果
现在再测试的时候,当你输入(await a()).,VS Code的IntelliSense就会正常弹出bar和foo两个键的提示,而且能正确识别它们的类型:bar是number,foo是Promise<RecursiveObject>。
为什么原来的代码不行?
原来的代码里,Custom类型只是一个简单的泛型传递,没有明确的递归结构声明。TypeScript的类型推断遇到循环引用时,会因为无法解析完整的类型链而直接放弃,导致IntelliSense没有任何提示。而显式声明递归类型别名后,TypeScript有了明确的类型锚点,就能正确解析循环引用的结构了。
内容来源于stack exchange

