首页 > 解决方案 > 如何在 TypeScript 中定义不透明类型?

问题描述

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

class Foo;

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

void printFoo(const Foo& foo);

然后,应用程序代码可能会使用对 Foo 的引用或指向 Foo 的指针,而不会看到 Foo 的实际定义。

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

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

interface Foo {};

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

标签: typescript

解决方案


那是因为 TypeScript 类型系统是“结构化的”,所以任何两种具有相同形状的类型都可以相互分配一个 - 而不是“名义”,其中引入一个新名称Foo会使其无法分配给相同的 -形状Bar类型,反之亦然。

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

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

// 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'.

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

declare const tag: unique symbol;
export type EUR = { readonly [tag]: 'EUR' };

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

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

品牌类型

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

declare const a: EUR;
const b: number = a; // OK

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

declare const tag: unique symbol
export type EUR = number & { readonly [tag]: 'EUR' };

例如,参见图书馆中的这种用法。io-ts


推荐阅读