[英]How to pass generic callback as a concrete argument?
TS Playground of the problem 问题的 TS 游乐场
function callStringFunction(callback: (s: string) => void) {
callback("unknown string inputted by user");
}
function callNumberFunction(callback: (n: number) => void) {
callback(4); // unknown number inputted by user
}
function genericFunction<T extends string | number>(value: T, genericCallback: (newValue: T) => void) {
if (typeof value === "string") {
console.log(value); // T is known to be a string here
callStringFunction(genericCallback); // But this throws an error: (newValue: T) => void not assignable to (s: string) => void
}
else {
console.log(value); // T is known to be a number here
callNumberFunction(genericCallback); // But this throws an error: (newValue: T) => void not assignable to (n: number) => void
}
}
How can I do this without resorting to: callNumberFunction(cb as (n: number) => void)
我怎么能不求助于:
callNumberFunction(cb as (n: number) => void)
Since the type for value
has been checked, shouldn't TypeScript be aware of the callback type as well?由于已经检查了
value
的类型,TypeScript 不应该也知道回调类型吗?
Unfortunately the TypeScript compiler is not able to understand what you are doing here.不幸的是 TypeScript 编译器无法理解你在这里做什么。 When you check
typeof value === "string"
, the compiler treats this as a type guard that narrows value
from T
to either string
or number
.当您检查
typeof value === "string"
时,编译器将其视为类型保护,将value
从T
缩小为string
或number
。 But it does nothing to narrow the type parameter T
itself.但它对缩小类型参数
T
本身没有任何作用。
You would like the compiler to see typeof value === "string"
and value: T
together and narrow T
from T extends string | number
您希望编译器同时看到
typeof value === "string"
和value: T
并且 narrow T
from T extends string | number
T extends string | number
to T extends string
or T extends number
or perhaps just specific string
and number
type. T extends string | number
to T extends string
或T extends number
或者只是特定的string
和number
类型。 This does not happen.这不会发生。
T
stays T
throughout the function, and so callStringFunction(genericCallback)
cannot be accepted. T
在整个 function 中保持T
,因此callStringFunction(genericCallback)
不能被接受。 The compiler still thinks that T
might be some arbitrary subtype of string | number
编译器仍然认为
T
可能是string | number
的任意子类型。 string | number
. string | number
。
There are open feature requests in GitHub to improve this situation somehow; GitHub 中有开放的功能请求以某种方式改善这种情况; see microsoft/TypeScript#33014 for example.
例如,参见microsoft/TypeScript#33014 。 Nothing has been implemented yet, though, partly because some "obvious" implementations would lead quickly to unsoundness.
不过,还没有任何实施,部分原因是一些“显而易见的”实施会很快导致不健全。
For example, if I had function f<T extends string | number>(value1: T, value2: T): void;
例如,如果我有
function f<T extends string | number>(value1: T, value2: T): void;
function f<T extends string | number>(value1: T, value2: T): void;
, I couldn't say that value1
being a string
implies that value2
is also a string
. ,我不能说
value1
是一个string
意味着value2
也是一个string
。 After all, maybe value1
and value2
are both string | number
毕竟,也许
value1
和value2
都是string | number
string | number
, as in the call f(Math.random()<0.99? "": 123, 123);
string | number
,如调用f(Math.random()<0.99? "": 123, 123);
. . There'd be a 99% chance of
value1
being a string
while value2
is a number
. value1
是string
而value2
是number
的可能性为 99%。 So any code to deal with this would need to be carefully considered.因此,需要仔细考虑处理此问题的任何代码。
Furthermore, I can't think of a good way to refactor your code so that the compiler can see what you're doing as safe.此外,我想不出重构代码的好方法,以便编译器可以安全地看到您正在做的事情。 The types
string
and number
are not considered discriminant property types, so I can't rewrite the args list to genericFunction()
as a discriminated union .类型
string
和number
不被视为可区分的属性类型,因此我无法将 args 列表重写为genericFunction()
作为可区分的 union 。 That would only work if you passed in a literal parameter like function genericFunction<K extends "str" | "num">(type: K, val: Val[K], cb: (x: Val[K])=>void): void;
这只有在你传入一个像
function genericFunction<K extends "str" | "num">(type: K, val: Val[K], cb: (x: Val[K])=>void): void;
这样的文字参数时才有效。 function genericFunction<K extends "str" | "num">(type: K, val: Val[K], cb: (x: Val[K])=>void): void;
. . In such a case you could check
type
and then the compiler would narrow val
and cb
together.在这种情况下,您可以检查
type
,然后编译器会将val
和cb
一起缩小。 But this isn't what you have, so it's not a straight refactoring.但这不是您所拥有的,因此这不是直接的重构。
For now I'd say that the best way to proceed is to use a type assertion to just tell the compiler that you know what you're doing.现在我会说最好的方法是使用类型断言来告诉编译器你知道你在做什么。 It's reasonable to write
val as Type
in cases where you are positive that val
really is of type Type
but the compiler cannot:在您确定
val
确实属于Type
但编译器不能的情况下,将val as Type
是合理的:
function genericFunction<T extends string | number>(
value: T, genericCallback: (newValue: T) => void
) {
if (typeof value === "string") {
console.log(value); // T is known to be a string here
callStringFunction(genericCallback as (newValue: string) => void);
}
else {
console.log(value); // T is known to be a number here
callNumberFunction(genericCallback as (newValue: number) => void);
}
}
This fixes the errors, but places the burden of ensuring type safety on you.这修复了错误,但将确保类型安全的负担加在了您身上。 It's not great, but it's the best I can figure out how to do as of now, without refactoring to an observably different algorithm.
这不是很好,但这是我目前能想到的最好的方法,无需重构为明显不同的算法。 Oh well!
那好吧!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.