如何通过参数区分TypeScript类型?实现Ref类型编译校验
实现TypeScript中不同对象引用的类型区分
当然可以实现这个需求!TypeScript 默认是结构类型系统,所以单纯的 string 类型会被视为同一类型,但我们可以通过**品牌化类型(Branded Types)**来模拟名义类型的行为,让 Ref<Blog> 和 Ref<User> 被编译器识别为完全不同的类型,即使它们的底层值都是字符串。
方法一:使用品牌属性标记类型
我们可以给 Ref 类型添加一个仅存在于类型层面的“品牌”属性,用来区分不同的引用类型:
// 定义通用的品牌类型工具 type Brand<BaseType, BrandIdentifier> = BaseType & { readonly __brand: BrandIdentifier }; // 重新定义你的Ref类型,用原对象类型作为品牌标记 type Ref<T extends { id: string }> = Brand<T['id'], T>; // 定义你的业务类型 interface Blog { id: string; title: string; } interface User { id: string; username: string; } // 创建引用的辅助函数(避免重复写类型断言) function createRef<T extends { id: string }>(id: T['id']): Ref<T> { return id as Ref<T>; } // 测试使用 const refBlog = createRef<Blog>('blog-1001'); const refUser = createRef<User>('user-2002'); // 这行代码会触发编译错误,符合预期! refBlog = refUser; // Error: Type 'Brand<string, User>' is not assignable to type 'Brand<string, Blog>'. // Types of property '__brand' are incompatible.
方法二:使用Symbol作为品牌标记(更安全)
如果担心 __brand 属性可能和其他业务属性冲突,可以用唯一的 Symbol 来作为品牌标记:
// 创建唯一的Symbol作为品牌标识 const RefBrand = Symbol('RefBrand'); // 定义带品牌的Ref类型 type Ref<T extends { id: string }> = T['id'] & { [RefBrand]: T }; // 同样的创建函数 function createRef<T extends { id: string }>(id: T['id']): Ref<T> { return id as Ref<T>; } // 测试效果和方法一完全一致 const refBlog = createRef<Blog>('blog-1001'); const refUser = createRef<User>('user-2002'); refBlog = refUser; // 编译错误
原理说明
这两种方法的核心都是利用 TypeScript 的结构类型特性:即使两个类型的底层值都是 string,只要它们的结构(这里是附加的品牌属性)不同,编译器就会将它们视为不同的类型,从而阻止错误的赋值操作。
需要注意的是,这个品牌标记只存在于编译阶段,不会在运行时给字符串添加实际的属性,所以完全不会影响代码的运行性能或逻辑。
内容的提问来源于stack exchange,提问作者Anton Medvedev




