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;
}
}
然后像这样定义与Data相关的类型和值PrimitiveData:
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 的实现确实需要一些跳跃。
好的,希望这可以帮助您继续前进。祝你好运!
Link to code