简体   繁体   English

如何将通用回调作为具体参数传递?

[英]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"时,编译器将其视为类型保护,将valueT缩小为stringnumber 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 stringT extends number或者只是特定的stringnumber类型。 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毕竟,也许value1value2都是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 . value1stringvalue2number的可能性为 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 .类型stringnumber不被视为可区分的属性类型,因此我无法将 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 ,然后编译器会将valcb一起缩小。 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!那好吧!

Playground link to code 游乐场代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM