简体   繁体   中英

How to define an opaque type in TypeScript?

If I recall correctly, in C++ you can define an an opaque type like this ...

class Foo;

... and use it like a handle eg when declaring function signatures ...

void printFoo(const Foo& foo);

Application code might then work with references-to-Foo or pointers-to-Foo without seeing the actual definition of Foo.

Is there anything similar in TypeScript -- how would you define an opaque type?

My problem is that if I define this ...

interface Foo {};

... then that's freely interchangeable with other similar types. Is there an idiom?

That is because TypeScript type system is "structural", so any two types with the same shape will be assignable one to each other - as opposed to "nominal", where introducing a new name like Foo would make it non-assignable to a same-shape Bar type, and viceversa.

There's this long standing issue tracking nominal typings additions to TS.

One common approximation of opaque types in TS is using a unique tag to make any two types structurally different:

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

Even better, the tag can be encoded with a unique Symbol to make sure it is never accessed and used otherwise:

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

Note that these representation don't have any effect at runtime, the only overhead is calling the eur constructor.

newtype-ts provides generic utilities for defining and using values of types that behave similar to my examples above.

Branded types

Another typical use case is to keep the non-assignability only in one direction, ie deal with an EUR type which is assignable to number :

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

This can be obtained via so called "branded types":

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

See for instance this usage in the io-ts library.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM