【问题标题】:TypeScript default function for higher-order function generic type高阶函数泛型类型的 TypeScript 默认函数
【发布时间】:2021-09-13 21:40:12
【问题描述】:

我有一个高阶函数,它根据作为参数传递的泛型函数正确推断其返回函数的类型,通过一个简单的示例可能更容易理解:

function someFunction<T>(f: (s: string) => T) {
  return function(s: string): T {
    return f(s)
  }
}

所以,如果我传递一个返回string 的函数,返回的函数将正确推断类型:

const funcStr = someFunction((s: string) => s)
//    ^ const funcStr: (s: string) => string

如果我传递一个返回 number 的函数,它将返回一个函数,该函数又返回一个数字:

const funcNum = someFunction((s: string) => Number(s))
//    ^ const funcStr: (s: string) => number

现在我想为高阶函数添加一个默认值,但无法使其工作:

// Type '(s: string) => number' is not assignable to type '(s: string) => T'.
function someFunction<T>(f: (s: string) => T = (s: string) => Number(s)) {
//                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  return function(s: string): T {
    return f(s)
  }
}

如何在高阶函数中添加默认函数,并且在使用另一个函数时仍使其推断类型?

Working TS playground.

【问题讨论】:

    标签: typescript types higher-order-functions


    【解决方案1】:

    从技术上讲,编译器抱怨是对的。您始终可以使用type assertion 来抑制默认函数参数上的错误,并为T 提供default type parameter,这样编译器就知道如果它无法从参数中推断出T 该怎么办:

    function someFunction<T = number>(
      f: (s: string) => T = ((s: string) => Number(s)) as any
    ) {
      return function (s: string): T {
        return f(s)
      }
    }
    

    这将完全按照您的意愿行事:

    const fNum = someFunction()
    console.log(fNum("3.14159").toFixed(2)); // 3.14
    const fStr = someFunction((s: string) => s)
    

    但这种方法的问题在于类型参数T 是由someFunction()调用者 指定的,而不是由实现指定的。所以没有什么能阻止恶意和/或困惑的调用者做这样的事情:

    const fOops = someFunction<string>();
    

    调用者已指定Tstring,尽管T 的默认值是number,但它已被显式string 类型覆盖。所以编译器认为fOops 的类型是(s: string) =&gt; string,但当然在运行时我们知道它不会是:

    try {
      fOops("oopsie").toUpperCase(); // no compiler error
    } catch (e) {
      console.log(e); // fOops(...).toUpperCase is not a function
    }
    

    这就是编译器抱怨的原因;意思是不能确定f 的默认值是否适合调用者指定的T

    如果您不关心这种可能性,那么这种方法很好,并且需要对您的代码进行最少的更改。


    如果您想防止出现类似fOops 的情况,您可以将someFunction() 更改为overloaded function 以拥有两个调用签名:

    // call signatures
    function someFunction(): (s: string) => number;
    function someFunction<T>(f: (s: string) => T): (s: string) => T;
    

    如果你在没有参数的情况下调用someFunction(),那么你调用的是第一个调用签名,返回类型必须是(s: string) =&gt; number;这个调用签名上根本没有泛型类型参数。

    否则,如果您使用回调参数调用它,那么您将调用第二个调用签名;有一个类型参数 T 对应于该回调的返回类型。

    那么实现可以和之前类似,但是你放宽了类型以防止投诉(这里我们将使用any作为返回类型):

    function someFunction(f: (s: string) => any = (s: string) => Number(s)) {
      return function (s: string) {
        return f(s)
      }
    }
    

    现在您打算支持的呼叫仍然有效:

    const fNum = someFunction()
    console.log(fNum("3.14159").toFixed(2)); // 3.14
    
    const fStr = someFunction((s: string) => s)
    

    但是不可能用类型参数调用零参数调用签名,所以你不能再用错误的方式调用它:

    const fOops = someFunction<string>() // error!
    // Expected 1 arguments, but got 0.
    

    Playground link to code

    【讨论】:

      猜你喜欢
      • 2012-10-25
      • 1970-01-01
      • 2020-05-17
      • 1970-01-01
      • 2018-07-03
      • 2023-03-10
      • 2020-03-11
      相关资源
      最近更新 更多