使用Extract工具类型收窄联合类型时出现类型错误
getCoinWithBalance与getAsset的类型不匹配 问题背景
尝试通过Extract工具类型,基于EthereumViewModel的泛型参数C(约束为'eth' | 'polygon'),从FlatAssetWithBalance联合类型中筛选对应链的资产类型,作为getCoinWithBalance的返回类型,但该类型与getAsset函数的返回类型不匹配,导致return行报错。
错误信息
类型 'BaseAsset & { chainId: C; contract: string; }' 不能赋值给类型 'EthChainFlatAssetWithBalance<C>'。 类型 'BaseAsset & { chainId: C; contract: string; }' 不能赋值给类型 'Extract<BaseAsset & { contract: string; chainId: "polygon"; }, { chainId: C; }>'。
类型不匹配的原因
EthChainFlatAssetWithBalance<C>的本质:它是通过Extract从FlatAssetWithBalance这个固定的联合类型中,筛选出chainId等于C的子类型。FlatAssetWithBalance是三个预先定义好的具体联合项,因此EthChainFlatAssetWithBalance<C>的结果是联合类型中的某一个具体成员(比如当C='polygon'时,就是BaseAsset & { contract: string; chainId: "polygon"; })。getAsset返回类型的本质:它是一个动态构造的泛型交叉类型——BaseAsset & { chainId: C; contract: string; },这个类型是基于泛型参数C实时组合出来的。
TypeScript的类型系统无法自动证明:对于任意符合约束的C,Extract出来的具体子类型和动态构造的交叉类型是完全等价的。尤其是当C是未具体化的泛型参数时,TypeScript会认为这两个类型属于不同的“类型构造方式”,因此拒绝赋值。
修复方案
方案1:调整EthChainFlatAssetWithBalance的定义(推荐)
直接让EthChainFlatAssetWithBalance<C>的类型和getAsset的返回类型对齐,放弃使用Extract,改用交叉类型构造:
// 替换原来的EthChainFlatAssetWithBalance定义 type EthChainFlatAssetWithBalance<C extends EthereumChainId> = BaseAsset & { chainId: C; contract: string; };
这样getCoinWithBalance的返回类型和getAsset的返回类型完全一致,类型错误会直接消失,同时代码更简洁、类型关系更明确。
方案2:使用类型断言(临时兼容)
如果无法修改EthChainFlatAssetWithBalance的定义,可以在返回时添加类型断言,明确告诉TypeScript这两个类型是等价的:
class EthereumViewModel<C extends EthereumChainId> { chainId: C = 'eth' as C; getCoinWithBalance(): EthChainFlatAssetWithBalance<C> { return getAsset(this.chainId) as EthChainFlatAssetWithBalance<C>; } }
这种方式适合无法修改原有类型定义的场景,但不如方案1优雅,因为它依赖开发者的类型判断,而非TypeScript的自动推导。
额外优化:简化FlatAssetWithBalance的定义
可以基于已有的BlockchainInfo类型重构FlatAssetWithBalance,避免重复代码:
type FlatAssetWithBalance = BaseAsset & BlockchainInfo & { amount: number };
这样FlatAssetWithBalance会自动继承BlockchainInfo的所有链类型定义,后续扩展链类型时无需重复修改。
内容的提问来源于stack exchange,提问作者Andre Medeiros

