繁体   English   中英

如何防止TypeScript中的文字类型

[英]How to prevent a literal type in TypeScript

假设我有一个这样的类,它包含一个值:

class Data<T> {
  constructor(public val: T){}

  set(newVal: T) {
    this.val = newVal;
  }
}

const a = new Data('hello');
a.set('world');
// typeof a --> Primitive<string>

到目前为止一切顺利,但现在我想将它限制为一组类型中的一个,让我们说原语:

type Primitives = boolean|string|number|null|undefined;

class PrimitiveData<T extends Primitives> {
  constructor(public val: T){}

  set(newVal: T) {
    this.val = newVal;
  }
}

const b = new PrimitiveData('hello');
b.set('world'); // Error :(

游乐场链接

最后一行失败是因为b是一个Primitive<'hello'>而不是一个Primitive<string> ,因此set只会将文字'hello'作为一个值,这显然不是我所追求的。

我在这做错了什么? 不依靠明确地扩大类型(例如: new Primitive<string>('hello') )我能做些什么吗?

TypeScript 故意在几乎所有地方推断文字类型 ,但通常会扩展这些类型,除非在少数情况下。 一种是当你有一个类型参数extends其中一个加宽的类型。 启发式是,如果你要求T extends string你可能会关心保持确切的字面值。 对于工会来说,这仍然是正确的,比如T extends Primitives ,所以你会得到这种行为。

我们可以使用条件类型强制(联合)字符串,数字和布尔文字扩展为(联合) stringnumberboolean

type WidenLiterals<T> = 
  T extends boolean ? boolean :
  T extends string ? string : 
  T extends number ? number : 
  T;

type WString = WidenLiterals<"hello"> // string
type WNumber = WidenLiterals<123> // number
type WBooleanOrUndefined = WidenLiterals<true | undefined> // boolean | undefined

现在这很好,你可能想要进行的一种方法是在PrimitiveData内使用WidenLiterals<T>代替T

class PrimitiveDataTest<T extends Primitives> {
  constructor(public val: WidenLiterals<T>){}
  set(newVal: WidenLiterals<T>) {
    this.val = newVal;
  }
}

const bTest = new PrimitiveDataTest("hello"); // PrimitiveDataTest<"hello">
bTest.set("world"); // okay

这样就可以了。 bTest的类型为PrimitiveDataTest<"hello"> ,但val的实际类型是string ,您可以这样使用它。 不幸的是,你得到了这种不良行为:

let aTest = new PrimitiveDataTest("goodbye"); // PrimitiveDataTest<"goodbye">
aTest = bTest; // error! 
// PrimitiveDataTest<"hello"> not assignable to PrimitiveDataTest<"goodbye">.
// Type '"hello"' is not assignable to type '"goodbye"'.

这似乎是由于TypeScript中的错误 ,其中没有正确检查条件类型。 PrimitiveDataTest<"hello">PrimitiveDataTest<"goodbye">结构上彼此相同并且与PrimitiveDataTest<string> ,因此类型应该是可相互分配的。 他们不是一个可能会或可能不会在不久的将来解决的错误(可能为TS3.5或TS3.6设置了一些修复?)

如果那没关系,那么你可能就此止步。


否则,您可能会考虑此实现。 定义像Data<T>这样的无约束版本:

class Data<T> {
  constructor(public val: T) {}
  set(newVal: T) {
    this.val = newVal;
  }
}

然后将类型和值PrimitiveData定义为与Data相关,如下所示:

interface PrimitiveData<T extends Primitives> extends Data<T> {}
const PrimitiveData = Data as new <T extends Primitives>(
  val: T
) => PrimitiveData<WidenLiterals<T>>;

名为PrimitiveData的类型和值对象就像一个泛型类,其中T被约束为Primitives ,但是当您调用构造函数时,生成的实例属于加宽类型:

const b = new PrimitiveData("hello"); // PrimitiveData<string>
b.set("world"); // okay
let a = new PrimitiveData("goodbye"); // PrimitiveData<string>
a = b; // okay

对于PrimitiveData用户来说,这可能更容易使用,尽管PrimitiveData的实现确实需要一些跳跃。


好的,希望能帮助你前进。 祝好运!

链接到代码

它非常难看,但你可以重载constructor并从类名中删除generic

class Data {
  constructor(public val: boolean)
  constructor(public val: string)
  constructor(public val: number)
  constructor(public val: null) {}

  set(newVal: boolean)
  set(newVal: string)
  set(newVal: number)
  set(newVal: null) {
    this.val = newVal;
  }
}

https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Functions.md#overloads

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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