我有一个带有泛型类型参数的抽象基类,如下所示:
abstract class Base<T> {
constructor(protected value: T) {}
}
我从基础继承了多个类。例如:
class TBase extends Base<number> {
constructor(value: number) {
super(value);
}
}
class XBase extends Base<string> {
constructor(value: string) {
super(value);
}
}
现在我想编写一个工厂函数,根据我的输入属性返回一个新的 Base:
type ValidTypes = 'string' | 'integer' | 'decimal' | 'boolean' | 'datetime';
type validFor<T> = T extends 'string' | 'datetime'
? string
: T extends 'integer' | 'decimal'
? number
: T extends 'boolean'
? boolean
: never;
function getBase<T extends ValidTypes, P extends validFor<T>>(type: T, value: P): Base<P> {
switch(type) {
case 'number': new TBase(coerceNumber(value)); break;
...
}
}
当传递 'string' 作为第一个参数时,第二个参数只能是 string 类型。对于 'decimal',输入 P 只能是数字。
但是我有两个问题。像这样调用函数时:
getBase('string', '5');
它有效,但它说签名是
function getBase<'string', '5'>(type: 'string', value: '5'): Base<'5'>
它不明白为什么它不解析为字符串而是解析为 value 的值?
另一个问题是,当我返回一个 new TBase() 时,它声明它也可以是一个字符串或布尔值:
"'number' 可分配给类型为 'P' 的约束,但可以使用约束 'string | number | boolean' 的不同子类型实例化 'P'。"
我对此进行了大量搜索,但无法绕过它。有人可以向我解释为什么会发生这种情况吗?即使我将它显式转换为 Base 它也会抛出错误(返回 new TBase() 作为 Base)
我尝试过的另一种方法是使用函数重载,但这看起来有点奇怪而且不对:
getBase(type: 'decimal' | 'integer', value: number): Base<number>
getBase(type: 'string' | 'datetime', value: string): Base<string>
getBase(type: 'boolean', value: boolean): Base<boolean>
getBase(type: ValidTypes, value: number | boolean | string): Base<number | boolean | string> {
...
}
我想要这样的东西:
getBase('string', 'thisMustBeAString'); // => Base<string>;
getBase('decimal', 54 /* Must be a number */) // => Base<number>;
我错过了什么?我很长时间都在为此苦苦挣扎..提前致谢
编辑:
答案 0 :(得分:0)
我非常努力地让你的第一种方法以完全类型安全的方式工作,没有强制转换,但失败了。
This question 还处理类型取决于参数类型的返回类型,并且有几个答案建议使用重载。正如您已经发现的那样,这是解决问题的一种方法 - 尽管您的实现的返回类型不应该是 Base<number | string | boolean>
而是 Base<number> | Base<string> | Base<boolean>
。
如果您可以稍微更改调用语法,那么您可以这样做并且仍然保持完全类型安全:
// Deliberately leaving this untyped, so we get the most specific inferred type possible.
const factories = {
'integer': (value: number) => new TBase(value) as Base<number>,
'string': (value: string) => new XBase(value) as Base<string>,
}
const a: Base<string> = factories['string']('thisMustBeAString');
const b: Base<number> = factories['integer'](54);
const c: Base<number> = factories['integer']('thisWillNotCompile');
const d: Base<number> = factories['string']('neitherWillThis');
const e: Base<string> = factories[a.value]('norWillThis');
从该映射中提取类型的一些方法:
// Extract the names of the types: 'integer' | 'string'.
type TypeName = keyof typeof factories;
// Extract the type of the factory function, for example (value: number) => Base<number>.
type FactoryType<T extends TypeName> = typeof factories[T];
// Extract the type of argument that goes into the factory.
type FactoryArgumentType<T extends TypeName> = Parameters<FactoryType<T>>[0];
// Extract the factory's return type.
// Currently always equal to its argument type, but it doesn't need to be.
type FactoryReturnType<T extends TypeName> = ReturnType<FactoryType<T>>;
使用这些,您实际上可以以一种相当丑陋的方式实现 getBase
:
function getBase<T extends TypeName>(type: T, value: FactoryArgumentType<T>): FactoryReturnType<T> {
return factories[type](value as never) as FactoryReturnType<T>;
}
我不太明白为什么需要奇怪的 value as never
才能让编译器接受它,但它最终会发出正确的代码。