You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何TypeScript品牌类型在索引类型比较中无法区分?

问题:品牌化字符串作为索引类型时无法正确区分类型

你遇到的问题是:虽然CatID和BirdID是互相不兼容的品牌化字符串类型,但使用它们作为索引键的对象类型在extends检查中却被判定为兼容,而直接检查两个品牌类型的兼容性是正确的(返回false)。

代码复现

type Brand<K, T> = K & { __type: T };

type CatID = Brand<string,'cat'>;
type BirdID = Brand<string,'bird'>;

// 意外返回true
const x1: {[id:CatID]:string} extends {[id:BirdID]:boolean} ? true : false;
const x2: {[id in CatID]:string} extends {[id in BirdID]:boolean} ? true : false;
const x3: Record<CatID,string> extends Record<BirdID,boolean> ? true : false;

// 正确返回false
const x4: CatID extends BirdID ? true : false;

原因分析

TypeScript对索引签名键类型的处理会忽略基于普通字符串属性的品牌标记:

  1. 你定义的Brand类型是通过将原始类型与{ __type: T }相交来模拟名义类型,但__type是一个普通的字符串字面量属性。
  2. 当这种品牌化类型被用作索引键时,TypeScript会“剥离”品牌标记,只保留底层的原始类型(这里是string)。
  3. 因此,{[id:CatID]:string}和{[id:BirdID]:boolean}都会被视为本质上的string索引签名类型,此时TypeScript会基于值类型的逆变规则进行兼容性检查,最终返回true。

解决方案:使用唯一Symbol作为品牌标记

要让TypeScript正确区分不同品牌化类型的索引键,需要使用唯一Symbol来定义品牌,因为Symbol的唯一性会被TypeScript保留,不会被剥离:

// 创建唯一的Symbol作为品牌标识
const CatBrand = Symbol('cat');
const BirdBrand = Symbol('bird');

// 基于Symbol定义品牌化类型
type CatID = string & { [typeof CatBrand]: never };
type BirdID = string & { [typeof BirdBrand]: never };

// 现在所有extends检查都返回正确的结果
const x1: {[id:CatID]:string} extends {[id:BirdID]:boolean} ? true : false;
//    ^? const x1: false

const x2: {[id in CatID]:string} extends {[id in BirdID]:boolean} ? true : false;
//    ^? const x2: false

const x3: Record<CatID,string> extends Record<BirdID,boolean> ? true : false;
//    ^? const x3: false

const x4: CatID extends BirdID ? true : false;
//    ^? const x4: false

这种方法通过唯一的Symbol属性确保TypeScript能识别不同品牌类型的结构差异,从而在索引签名的兼容性检查中正确区分它们。


内容的提问来源于stack exchange,提问作者Nimai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 19:45:34