如何在 TypeScript 中定义不透明类型?

Chr*_*isW 21 typescript

如果我没记错的话,在 C++ 中你可以定义一个像这样的不透明类型......

class Foo;
Run Code Online (Sandbox Code Playgroud)

...并像句柄一样使用它,例如在声明函数签名时...

void printFoo(const Foo& foo);
Run Code Online (Sandbox Code Playgroud)

然后,应用程序代码可能会在没有看到 Foo 的实际定义的情况下使用对 Foo 的引用或对 Foo 的指针。

TypeScript 中是否有类似的东西——你会如何定义一个不透明的类型?

我的问题是,如果我定义这个......

interface Foo {};
Run Code Online (Sandbox Code Playgroud)

...然后可以与其他类似类型自由互换。有成语吗?

Gio*_*aga 26

那是因为 TypeScript 类型系统是“结构化的”,因此具有相同形状的任何两种类型都可以相互分配——而不是“名义”,在这种情况下,引入一个新名称 likeFoo将使其不可分配给相同的名称——形状Bar类型,反之亦然。

跟踪 TS 的名义类型添加是一个长期存在的问题

TS 中 opaque 类型的一种常见近似是使用唯一的标签来使任何两种类型在结构上不同:

// opaque type module:
export type EUR = { readonly _tag: 'EUR' };
export function eur(value: number): EUR {
  return value as any;
}
export function addEuros(a: EUR, b: EUR): EUR {
  return ((a as any) + (b as any)) as any;
}

// usage from other modules:
const result: EUR = addEuros(eur(1), eur(10)); // OK
const c = eur(1) + eur(10) // Error: Operator '+' cannot be applied to types 'EUR' and 'EUR'.
Run Code Online (Sandbox Code Playgroud)

更好的是,标签可以用唯一的 Symbol 进行编码,以确保它永远不会被访问和使用:

declare const tag: unique symbol;
export type EUR = { readonly [tag]: 'EUR' };
Run Code Online (Sandbox Code Playgroud)

请注意,这些表示在运行时没有任何影响,唯一的开销是调用eur构造函数。

newtype-ts提供了通用实用程序,用于定义和使用行为类似于我上面的示例的类型的值。

品牌类型

另一个典型用例是仅在一个方向上保持不可分配性,即处理EUR可分配给的类型number

declare const a: EUR;
const b: number = a; // OK
Run Code Online (Sandbox Code Playgroud)

这可以通过所谓的“品牌类型”获得:

declare const tag: unique symbol
export type EUR = number & { readonly [tag]: 'EUR' };
Run Code Online (Sandbox Code Playgroud)

例如,请参阅库中的这种用法io-ts

  • 另请参阅这篇精彩的[关于品牌与风味的博客文章](https://spin.atomicobject.com/2018/01/15/typescript-flexible-nominal-typing/) (2认同)