TypeScript 小技巧

最后更新于

Implicit Nominal Typing

不知道中文该叫什么,隐式标称类型?咳咳,简单来说我们想标记一类特定的对象是某个类型的,但是又没有也不想在他身上加个脏脏的标记字段,如下面的例子:

class Foo {}

class Bar {}

let foo: Foo = new Bar()

明明是两个类,但是其中一个的实例居然可以直接赋值成另一个类型。为了不让这么操作,可以要求 Foo 身上有个 Bar 身上没有的东西,可以往他身上添加一个不存在的私有属性来达到目的:

class class FooFoo {
  private declare Foo._isFoo: true_isFoo: true
}

class class BarBar {}

let foo: class FooFoo = new constructor Bar(): BarBar()
Property '_isFoo' is missing in type 'Bar' but required in type 'Foo'.

这里有两个标记使得他变得如此好用:

如果你设置了 useDefineForClassFields: false,也可以省略 declare 换成 !

Shared Nominal Typing

上文往 class 上添加了私有属性,如果是往 interface 上加呢?诶,我们发现 private 是保不住了,但是可以让一些类共享同一个类型,而且用户一般不会自己尝试实现这个类型:

type 
type FooLike = {
    _isFoo: FooLike;
}
FooLike
= { _isFoo: FooLike_isFoo:
type FooLike = {
    _isFoo: FooLike;
}
FooLike
}
class class BarBar { declare Bar._isFoo: this_isFoo: this } let let a: FooLikea:
type FooLike = {
    _isFoo: FooLike;
}
FooLike
= new constructor Bar(): BarBar()
class class UserDefinedUserDefined {} let b:
type FooLike = {
    _isFoo: FooLike;
}
FooLike
= new constructor UserDefined(): UserDefinedUserDefined()
Property '_isFoo' is missing in type 'UserDefined' but required in type 'FooLike'.

Tagged Value

传统 JavaScript 人的做法是存一个 { type: string, value },写起来不免有些繁琐而且不利于代码压缩。我们可以考虑下面这个写法:

class Value<T> {
  /** @internal */
  constructor(readonly type: Type<T>, readonly value: T) {}

  static define<T>() {
    return new Type<T>()
  }

  is<T>(type: Type<T>): this is Value<T> {
    return this.type === type
  }
}

class Type<T> {
  of(value: T): Value<T> {
    return new Value(this, value)
  }
}

// 定义一个类型叫温度
const temperature = Value<number>.define()

// 实例化这个类型
const a = temperature.of(42)
a.is(temperature) // true

注意到 @internal 标记,这使得最终用户不能看到这个构造,也就可以要求一定通过指定的工厂函数实例化自定义类型了。