高阶函数泛型类型的 TypeScript 默认函数
TypeScript default function for higher-order function generic type
我有一个高阶函数,它根据作为参数传递的通用函数正确推断其 returned 函数的类型,用一个简单的例子可能更容易理解:
function someFunction<T>(f: (s: string) => T) {
return function(s: string): T {
return f(s)
}
}
因此,如果我传递 return 是 string
的函数,returned 函数将正确推断类型:
const funcStr = someFunction((s: string) => s)
// ^ const funcStr: (s: string) => string
如果我传递一个 return 是 number
的函数,它将 return 一个 return 又是一个数字的函数:
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)
}
}
如何向高阶函数添加默认函数,并且在使用另一个函数时仍能推断出类型?
从技术上讲,编译器的抱怨是对的。您始终可以将 type assertion to suppress the error on the default function parameter, as well as providing a default type parameter 用于 T
,以便编译器在无法从参数推断 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()
的调用者指定的,而不是由实现指定的。因此,没有什么能阻止恶意 and/or 困惑的调用者做这样的事情:
const fOops = someFunction<string>();
调用者已指定 T
是 string
,即使 T
的默认值是 number
,它已被显式 string
类型。所以编译器认为 fOops
是 (s: string) => 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()
,则调用第一个调用签名,return类型必须是(s: string) => number
;此调用签名上根本没有泛型类型参数。
否则,如果您使用回调参数调用它,那么您将调用第二个调用签名;有一个类型参数 T
对应于该回调的 return 类型。
然后实现可以和之前类似,但是你放宽了类型以防止投诉(这里我们将使用 any
作为 return 类型):
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.
我有一个高阶函数,它根据作为参数传递的通用函数正确推断其 returned 函数的类型,用一个简单的例子可能更容易理解:
function someFunction<T>(f: (s: string) => T) {
return function(s: string): T {
return f(s)
}
}
因此,如果我传递 return 是 string
的函数,returned 函数将正确推断类型:
const funcStr = someFunction((s: string) => s)
// ^ const funcStr: (s: string) => string
如果我传递一个 return 是 number
的函数,它将 return 一个 return 又是一个数字的函数:
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)
}
}
如何向高阶函数添加默认函数,并且在使用另一个函数时仍能推断出类型?
从技术上讲,编译器的抱怨是对的。您始终可以将 type assertion to suppress the error on the default function parameter, as well as providing a default type parameter 用于 T
,以便编译器在无法从参数推断 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()
的调用者指定的,而不是由实现指定的。因此,没有什么能阻止恶意 and/or 困惑的调用者做这样的事情:
const fOops = someFunction<string>();
调用者已指定 T
是 string
,即使 T
的默认值是 number
,它已被显式 string
类型。所以编译器认为 fOops
是 (s: string) => 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()
,则调用第一个调用签名,return类型必须是(s: string) => number
;此调用签名上根本没有泛型类型参数。
否则,如果您使用回调参数调用它,那么您将调用第二个调用签名;有一个类型参数 T
对应于该回调的 return 类型。
然后实现可以和之前类似,但是你放宽了类型以防止投诉(这里我们将使用 any
作为 return 类型):
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.