如何为原生JavaScript重载添加类型注解以避免调用错误
问题描述
项目已启用JavaScript类型检查,jsconfig.json配置如下:
{ "compilerOptions": { "target": "ES2020", "checkJs": true, "allowJs": true, "strictNullChecks": false, "strictFunctionTypes": true, "module": "NodeNext" }, "exclude": [ "node_modules", "**/node_modules/*", "**/purest/build/*" ] }
日常开发中常使用支持多类型的“重载”逻辑,要么根据类型执行不同操作,要么将输入转换为统一类型。以下是转换为统一值的场景示例:
class AccountInfo { /** * * @param {string} accountName */ constructor(accountName, someFlag) { this.accountName = accountName; this.someFlag = someFlag; } } /** * * @param {AccountInfo[]|string[]|string} accountNames * @returns {boolean} */ function createAccounts(accountNames) { if(typeof accountNames == "string") { accountNames = [accountNames]; } for(let i=0; i<accountNames.length; ++i) { // 报错:Argument of type 'string | AccountInfo' is not assignable to parameter of type 'string'. // Type 'AccountInfo' is not assignable to type 'string'.ts(2345) const acc = accountNames[i]; if(typeof acc == "string") { accountNames[i] = new AccountInfo(acc, false); } } /** * * @param {AccountInfo} accountInfo */ function doWithAccountInfo(accountInfo) { // 处理accountInfo逻辑 } for(const acc of accountNames) { // 报错:Argument of type 'string | AccountInfo' is not assignable to parameter of type 'AccountInfo'. // Type 'string' is not assignable to type 'AccountInfo'.ts(2345) doWithAccountInfo(acc); } return true; }
尝试用/** @type {AccountInfo[]} **/强制转换变量类型但无效,也可通过将accountNames赋值给新变量解决,但不想为适配类型系统修改运行时代码。希望在保留弱类型优势的同时获得类型检查的益处。
解决方案
1. 正确使用内联JSDoc类型断言
之前的断言写法有误,正确的内联断言需用单星号注释,且直接包裹目标变量。无需修改业务逻辑,仅在使用时添加断言即可:
for(const acc of /** @type {AccountInfo[]} */ (accountNames)) { doWithAccountInfo(acc); }
这种方式不会改变运行时行为,只是告诉TypeScript当前变量的实际类型。
2. 自定义JSDoc类型守卫
通过类型守卫函数让TypeScript自动识别转换后的变量类型,无需手动断言:
/** * @param {AccountInfo[]|string[]} arr * @returns {arr is AccountInfo[]} */ function isAccountInfoArray(arr) { return arr.every(item => item instanceof AccountInfo); } function createAccounts(accountNames) { if(typeof accountNames == "string") { accountNames = [accountNames]; } for(let i=0; i<accountNames.length; ++i) { const acc = accountNames[i]; if(typeof acc == "string") { accountNames[i] = new AccountInfo(acc, false); } } // 类型守卫触发后,TypeScript自动推导accountNames为AccountInfo[]类型 if (isAccountInfoArray(accountNames)) { for(const acc of accountNames) { doWithAccountInfo(acc); } } return true; }
类型守卫函数仅用于辅助类型推导,运行时可保留也可通过构建工具移除,完全不影响原有业务逻辑。
3. 配置JSDoc函数重载
通过@overload标签定义函数的多类型输入场景,让TypeScript更精准地推导内部类型:
/** * @overload * @param {string} accountNames * @returns {boolean} */ /** * @overload * @param {string[]} accountNames * @returns {boolean} */ /** * @overload * @param {AccountInfo[]} accountNames * @returns {boolean} */ /** * @param {AccountInfo[]|string[]|string} accountNames * @returns {boolean} */ function createAccounts(accountNames) { if(typeof accountNames == "string") { accountNames = [accountNames]; } for(let i=0; i<accountNames.length; ++i) { const acc = accountNames[i]; if(typeof acc == "string") { accountNames[i] = new AccountInfo(acc, false); } } for(const acc of /** @type {AccountInfo[]} */ (accountNames)) { doWithAccountInfo(acc); } return true; }
函数重载既能优化外部调用的类型检查体验,结合内联断言也能解决内部类型推导问题,全程无需修改运行时代码。
内容的提问来源于stack exchange,提问作者Tomáš Zato
相关产品推荐
相关产品推荐

