[英]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
,所以你会得到这种行为。
我们可以使用条件类型强制(联合)字符串,数字和布尔文字扩展为(联合) string
, number
和boolean
:
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.