[英]Typescript: how do I most explicitly define a variable that can hold a function name or that function's return type?
I'm refactoring/rebuilding an existing codebase, and came across the equivalent of this code:我正在重构/重建现有的代码库,并遇到了这段代码的等价物:
class FeedbackBase {
Variables = [{Value: 'doSomething'}];
goToFeedback() {
this.Variables?.forEach(variable => {
if (variable.Value) {
variable.Value = (this[variable.Value])();
}
});
}
doSomething(): string { return '123';}
}
The basic idea is that Variables[].Value
might contain a function call defined on FeedbackBase
.基本思想是
Variables[].Value
可能包含在FeedbackBase
上定义的 function 调用。 If it does, then run the function, and replace Variables[].Value
with the return value.如果是,则运行 function,并将
Variables[].Value
替换为返回值。
Before this.Variables?.forEach
, they were originally doing a deep copy of this.Variables
, wiping out all of the typing and basically running the code on an 'any' object. When we keep the typing, Typescript complains about this[variable.Value]
:在
this.Variables?.forEach
之前,他们最初是在做this.Variables
的深度复制,消除所有输入并基本上在“任何”object 上运行代码。当我们继续输入时,Typescript 抱怨this[variable.Value]
:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'FeedbackBase'. No index signature with a parameter of type 'string' was found on type 'FeedbackBase'.
Fair enough: all Typescript knows is that the contents of variable.Value
are a string
: it doesn't know whether it is a callable function on FeedbackBase
.很公平:Typescript 只知道
variable.Value
的内容是一个string
:它不知道它是否是FeedbackBase
上的可调用 function。
My potential solution:我的潜在解决方案:
type BaseFunction = {
doSomething(): string;
};
class FeedbackBase implements BaseFunction {
Variables = [{Value: 'doSomething'}];
goToFeedback() {
this.Variables?.forEach(variable => {
if (variable.Value) {
variable.Value = this[variable.Value as keyof BaseFunction]();
}
})
}
doSomething(): string { return '123';}
}
Basically:基本上:
BaseFunction
, explicitly define all of the functions that could be defined in variable.Value
BaseFunction
中,显式定义所有可以在variable.Value
中定义的函数FeedbackBase implements BaseFunction
this[variable.Value as keyof BaseFunction]
to tell Typescript that this should be treated as a function call, not as a string.this[variable.Value as keyof BaseFunction]
告诉 Typescript 这应该被视为 function 调用,而不是字符串。 A nice feature of this approach is that when you say this[variable.Value as keyof BaseFunction]
, Typescript checks to make sure that this
indeed has all of the functions defined in BaseFunction
.这种方法的一个很好的特点是,当你说
this[variable.Value as keyof BaseFunction]
时,Typescript 会检查以确保this
确实具有BaseFunction
中定义的所有函数。 (For example, if you change FeedbackBase.doSomething
to FeedbackBase.doSomethings
, an error appears at this[variable.Value as keyof BaseFunction]
). (例如,如果您将
FeedbackBase.doSomething
更改为FeedbackBase.doSomethings
,则会在this[variable.Value as keyof BaseFunction]
处出现错误)。
I think that, in this case, I'm forced to use as
, because variable.Value
could be keyof BaseFunction
, or whatever is returned by the function call.我认为,在这种情况下,我不得不使用
as
,因为variable.Value
可能是keyof BaseFunction
,或者 function 调用返回的任何内容。 Even if I get more specific about the return type, I can't think of a way to define variable.Value
as definitely a function call before the transformation, and definitely a return type afterward, outside of just using as
.即使我对返回类型有更具体的了解,我也想不出一种方法来定义
variable.Value
在转换之前绝对是一个 function 调用,并且在转换之后绝对是一个返回类型,除了使用as
之外。
Is there a way to be more explicit?有没有办法更明确?
I don't know if this is the best TypeScript per say because it seems a bit convoluted, but I think your solution is fine in terms of typings.我不知道这是否是最好的 TypeScript 因为它看起来有点复杂,但我认为你的解决方案在打字方面很好。 I would beware of one thing though.
我会提防一件事。
I don't think the forEach
does what you want it to.我不认为
forEach
会做你想做的事。 If a Value
is not a function, you'll get a runtime error because you're not actually checking if Value
is a function. Instead, you're checking if it exists.如果
Value
不是 function,您将收到运行时错误,因为您实际上并未检查Value
是否为 function。相反,您正在检查它是否存在。
this.Variables?.forEach((variable) => {
if (
variable.Value &&
variable.Value in this &&
typeof this[variable.Value as keyof BaseFunction] === "function"
) {
variable.Value = this[variable.Value as keyof BaseFunction]()
}
})
TS would not allow you to do that if it knew what you wanted to do, and I think it would be right in complaining.如果 TS 知道你想做什么,它不会允许你这样做,我认为抱怨是正确的。 This is what this answer will attempt to demonstrate.
这就是这个答案将试图证明的。
You don't need an interface to wire the types.您不需要接口来连接类型。 You can statically work out the names of
FeedbackBase
non-void method names like so:您可以像这样静态地计算出
FeedbackBase
非空方法名称的名称:
type NonVoidMethodNames<Class> = keyof {
[K in keyof Class
as ((...args: any[]) => void) extends Class[K] ? never
: Class[K] extends (...args: any[]) => unknown ? K
: never
]: K
}
type DoSomething = NonVoidMethodNames<FeedbackBase> // "doSomething"
If you assigned it to Variables
like the following, you would not need your as
when you call this[variable.Value]()
.如果您将它分配给如下所示的
Variables
,则在调用this[variable.Value]()
时不需要您的as
。
Variables: { Value: NonVoidMethodNames<FeedbackBase> }[] = [{ Value: 'doSomething' }];
TS would try to index this
with "doSomething"
, realise it's a method, no problem, and work out the return type as string
as expected TS 会尝试用
"doSomething"
索引this
,意识到这是一个方法,没问题,并按预期将返回类型计算为string
But of course we can't simply do that because now we're assigning string
to variable.Value
which we had defined to be "doSomething"
.但当然我们不能简单地这样做,因为现在我们将
string
分配给variable.Value
我们定义为"doSomething"
。
In TS, you can't write the following and this is the situation you are in, only with indirections in-between.在 TS 中,您不能编写以下内容,这就是您所处的情况,只能在两者之间进行间接访问。
let foo: string = 'a';
foo = 1;
You would also be inconvenienced if you wanted to widen Value
to MethodName | ReturnType
如果您想将
Value
扩大到MethodName | ReturnType
,您也会感到不便。 MethodName | ReturnType
when the return type is string
as it is the case here because string
would swallow its subtypes. MethodName | ReturnType
当返回类型是string
时,就像这里的情况一样,因为string
会吞下它的子类型。
Even ignoring the problem of when Variables
is populated, the user has no way to know what values this field contains.即使忽略
Variables
何时填充的问题,用户也无法知道该字段包含什么值。 This is a deal breaker in my opinion.在我看来,这是一个交易破坏者。 I am not very savvy in OO but I would say this design is flawed and cannot be typed.
我对 OO 不是很了解,但我会说这种设计有缺陷,无法输入。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.