我觉得我基本上误解了品牌类型.以下是目前为止我的代码:

type BrandedString = string & { __brand: true };

function brandString(value: string): BrandedString {
  return value as BrandedString;
}

function isBrandedString(value: string): value is BrandedString {
  return (value as any)["__brand"];
}

let x = brandString("hello");
console.log("brandString", x, isBrandedString(x)); // returns false

如果我将鼠标悬停在'x'上,它确实显示(在intellisense中)类型现在是BrandedString,但是如果我试图调用我的try 类型保护"IsBrandedString",我会得到false.

我想实现isBrandedString,这样我就可以判断输入是否是brandedType,但我似乎无法得到它.

...
return (value as any)["__brand"] === true
return (value as any)["__brand"]
return (value as any).__brand !== undefined;
return value[__brand] !== undefined; // compilation error

如何判断给定的输入是否为BrandedString?

推荐答案

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.__myStringtrue也没什么特别的.只是随便放些东西来区别其他东西.如果您想创建其他品牌字符串类型,您可以 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 assertionlie发送到编译器,以获得这些标记原语中的一个:

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这样的东西的全部意义是为了防止你在自己的代码中不小心混淆不同的"类型"字符串.它更像是你添加到类型中的一个心理标签,以帮助你跟踪事情.标签只存在于你的值概念中,而不是实际值中.


所以现在你得决定为什么要这么做.如果您需要在运行时实际标记一个值,以便在运行时区分stringBrandedString,那么您几乎需要放弃原语.可以使用对象类型,

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

Typescript相关问答推荐

无需刷新即可让现场访客逆变

使用前的组件渲染状态更新

在这种情况下,如何获得类型安全函数的返回值?

typescribe不能使用值来索引对象类型,该对象类型满足具有该值的另一个类型'

使某些类型的字段变为可选字段'

如何修复正在处理类型但与函数一起使用时不处理的类型脚本类型

如何使用泛型类型访问对象

接口的额外属性作为同一接口的方法的参数类型'

如果请求是流,如何等待所有响应块(Axios)

泛型函数中输入参数类型的推断问题

material 表不渲染任何数据

在映射类型中用作索引时,TypeScrip泛型类型参数无法正常工作

匿名静态类推断组成不正确推断

如果字符串文字类型是泛型类型,则用反引号将该类型括起来会中断

如何使用类型谓词将类型限制为不可赋值的类型?

使用2个派生类作为WeakMap的键

如何创建由空格连接的多个字符串的类型

缩小对象具有某一类型的任意字段的范围

类型脚本中参数为`T`和`t|unfined`的重载函数

为什么 Typescript 无法正确推断数组元素的类型?