假设我有一个这样的类,它包装了一个值:
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')
),我能做些什么?
答案 0 :(得分:2)
TypeScript intentionally infers literal types just about everywhere,但除少数情况外,通常会扩展这些类型。一种是当您拥有extends
其中一种扩展类型的类型参数时。启发式方法是,如果您要求输入T extends string
,则可能需要保留确切的文字。像T extends Primitives
这样的联合也是如此,因此您会得到这种行为。
我们可以使用conditional types强制将字符串,数字和布尔文字的(联合)扩展为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
现在这很好,您可能要进行的一种方法是在WidenLiterals<T>
内的任何地方使用T
代替PrimitiveData
:
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中的bug导致无法正确检查条件类型。类型PrimitiveDataTest<"hello">
和PrimitiveDataTest<"goodbye">
彼此互为structurally identical to,并分别为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
的用户来说可能更容易使用。
好的,希望能帮助您前进。祝你好运!
答案 1 :(得分:1)
这很丑陋,但是您可以重载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