You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何通过参数区分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

火山引擎 最新活动