TypeScript的类型系统主要是structural,而不是nominal.这意味着具有相同 struct 的两个类型被视为相同类型. 这两种类型是否具有不同的名称或在不同的地方声明并不重要. TypeScript允许您创建type aliases,但这些只是现有类型的不同名称,并不用于区分类型或创建新类型.只是同一个东西的另一个名字:
type MyString = string;
const x: string = "abc";
const y: MyString = x; // okay, no difference between string and MyString
但有时人们会在TypeScript中有名义类型.您可以通过向类型添加一些随机的区别 struct 来模拟名义类型,即使该 struct 实际上在运行时并不对应任何东西.
对于像string
这样的primitives,你甚至在运行时添加了这样的 struct ,因为它们实际上并不保存属性(它们appear通过自动装箱来拥有方法和属性,但这只是给了你包装类的原型上的东西,比如String
).但这并不妨碍我们写出这样一个类型,就好像它确实存在一样,这给了我们branded primitives:
type MyString = string & { __myString: true };
const x: string = "abc";
const y: MyString = x; // error!
// ~ <-- Type 'string' is not assignable to type 'MyString'.
现在,MyString
类型在 struct 上与string
不同,因为它假定具有类型true
的__myString
属性. 所以你不能再意外地将string
分配给MyString
.__myString
和true
也没什么特别的.只是随便放些东西来区别其他东西.如果您想创建其他品牌字符串类型,您可以 Select 不同的品牌属性.
这一切都很好,只是我们在运行时实际上无法获得类型MyString
的值.图元不能像对象那样保存属性:
y.__myString = true; // you can write this, but it doesn't work
// 💥 TypeError! can't assign to property "__myString" on "abc": not an object
您可以使用type assertion将lie发送到编译器,以获得这些标记原语中的一个:
const z: MyString = "def" as MyString; // okay
这允许你在值as if之间移动,它们被标记,但没有运行时的影响. 整个TypeScript类型系统在编译时为erased,所以as MyString
消失了.TypeScript有type assertions,而不是type casting(或者至少,它的类型的强制转换不应该与其他语言,如Java或C中的强制转换混淆,因为它们实际上在运行时做一些事情).
因此the only use of a branded primitive is to help developers keep track of the different uses of the same underlying primitive at compile time. There is no runtime test you can perform to determine if a value is branded. No runtime原语实际上被标记.
所以你的brandString
功能
function brandString(value: string): BrandedString {
return value as BrandedString;
}
只返回其输入,没有运行时效果.你不能写一个isBrandedString()
type guard function的工作:
function isBrandedString(value: string): value is BrandedString {
// impossible
}
BrandedString
这样的东西的全部意义是为了防止你在自己的代码中不小心混淆不同的"类型"字符串.它更像是你添加到类型中的一个心理标签,以帮助你跟踪事情.标签只存在于你的值概念中,而不是实际值中.
所以现在你得决定为什么要这么做.如果您需要在运行时实际标记一个值,以便在运行时区分string
和BrandedString
,那么您几乎需要放弃原语.可以使用对象类型,
type BrandedString = { value: string; __brand: true };
function brandString(value: string): BrandedString {
return { value, __brand: true };
}
function isBrandedString(value: unknown): value is BrandedString {
return !!value && typeof value === "object"
&& "__brand" in value && value.__brand === true;
}
let x = brandString("hello");
console.log("brandString", x, isBrandedString(x)); // returns true
但当然,现在你只是在一个容器中的holding a string
,你在容器上贴上标签,而不是在字符串上.这可能不是你想做的,但它有实际工作的好处.
Playground link to code