我知道如何使用参数类型处理动态类型检查,但我坚持使用这个。这可能是不可能的,但我不能再直接思考了,所以欢迎任何帮助!
给定代码:
class DefaultClass {
defaultProp: number;
constructor(num: number) {
this.defaultProp = num;
}
}
class MyClass extends DefaultClass {
myProp ?: string;
constructor(num: number, str ?: string) {
super(num);
this.myProp = str || "default value";
}
myMethod(str: string): string[] {
return [str];
}
}
interface Config {
class: typeof DefaultClass;
}
const config: Config = {
class: DefaultClass
}
function initConfig(classType: typeof DefaultClass) {
config.class = classType;
}
// we don't care for the param type, it's irrelevant here
function myFunction(param: any): InstanceType<typeof config.class> {
// does something based on which class is used in config
// returns a DefaultClass or MyClass instance based on the current config
return new config.class(1);
}
initConfig(MyClass);
const myInstance: MyClass = myFunction("something");
// ERR !: Property 'myMethod' is missing in type 'DefaultClass'
// but required in type 'MyClass'.
正如您猜测的那样,静态类型检查无法根据对 config
对象所做的更改动态更改返回类型,因为它在运行之前是“未知的”。但我想找到一种方法(如果有的话)来做到这一点。
答案 0 :(得分:1)
直接在 TypeScript 中支持你正在做的事情的一个大问题是它需要:
无任意类型突变
TypeScript 不支持任意改变表达式或变量的类型。没有办法做类似 let foo = {a: "hello"}; foo = {b: 123};
的事情并且让编译器将 foo
的明显类型从 {a: string}
更改为 {b: number}
。您可以像 foo
这样注释 let foo: {a?: string; b?: number}
的类型,但是编译器不会注意到从一个赋值到下一个赋值发生了任何变化。
它所做的只有narrowing via control flow analysis。因此,如果您有某个已知类型的值,则可以通过赋值或其他工具(如 user-defined type guards 或 assertion functions)使变量的表观类型更加具体。因此,您可以想象地让编译器注意到 config
已从类型 {class: typeof Defaultclass}
缩小到 {class: typeof MyClass}
。但是:
跨函数边界没有类型缩小效果
它对您没有帮助,因为您希望 config
的调用者可以看到 myFunction()
类型的更改。这不会发生,因为 myFunction()
只会看到 config
类型直接发生在 myFunction()
的主体内部的变化。控制流缩小不会跨越函数边界持续存在,因为不可能实现既有效又不会严重降低编译器性能的通用解决方案。有一个 GitHub 问题,microsoft/TypeScript#9998,讨论了在调用函数时尝试处理缩小效应的问题。
所以你有点卡住了。
我的建议不是这样做,而是:如果您希望 TypeScript 帮助您,请做它理解的事情。对变量做的最好的事情就是在它的整个生命周期中保持相同的类型。您的代码的含义是永远不要通过调用 config
来修改 initConfig()
,而是使用 initConfig
的参数来create myFunction()
。这将不得不被打包成一种工厂,吐出一个实现。我们可以使用 class
:
class Impl<T extends typeof DefaultClass = typeof DefaultClass> {
class: T
constructor(classType: T = DefaultClass as T) {
this.class = classType;
}
myFunction(param: any) {
return new this.class(1) as InstanceType<T>;
}
}
const CurImpl = new Impl(MyClass);
const myInstance: MyClass = CurImpl.myFunction("something");
const DifferentImpl = new Impl();
const differentInstance: DefaultClass = DifferentImpl.myFunction("else");
这里 CurImpl
知道 MyClass
因为它是 Impl<typeof MyClass>
的一个实例,并且它的类型永远不会改变。如果您想使用不同的类构造函数,您必须为某些 Impl<T>
创建一个新的 T
实例。
在上面,顺便说一句,我使用了 generic parameter default,因此如果无法推断出 T
,它将使用 typeof DefaultClass
。如果您不传入 classType
参数,编译器将使用 DefaultClass
。这在技术上不是类型安全的,因为如果您不传入参数,编译器无法确定 T
实际上是 typeof DefaultClass
。有人可以调用 new Impl<typeof MyClass>()
并破坏事物。假设没有人会做这样的事情,我使用 type assertion 只是告诉编译器,如果 classType
没有传入,那么 DefaultClass
值可以分配给类型 {{1} }.可能有更多类型安全的方法来做到这一点(可能没有 T
),但我不想像所问的那样离题太远。