鉴于下面的示例,是否有任何方法可以实现类似于
foobarA
的 DRY 代码?
interface F<T, U extends string> { t: T, f: (u: U) => void }
declare const foo: <T, U extends string>(type: U) => F<T, U>;
// the following correctly types foobar, however 'bar' redundantly repeats.
const foobar = foo<string, 'bar'>('bar')
// ideally one would want the generic inference to be smarter and the
// following to work:
const foobarA = foo<string>('bar')
TypeScript 不直接支持 microsoft/TypeScript#26242 中要求的部分类型参数推断。因此,如果您有以下形式的generic函数:
declare const foo: <T, U extends string>(u: U) => F<T, U>;
没有办法调用它,所以你手动指定
T
但让编译器推断 U
:
// const foobarA: F<string, unknown> ☹
const foobar = foo<string, "bar">("bar");
您必须手动指定两个类型参数,如
foo<string, "bar">("bar");
中所示(不幸的是这是多余的),或者让编译器推断两个参数,如 foo("bar")
中所示(这不会推断出 T
的正确类型)。
除非这种情况发生变化,否则您将需要解决它。
一种解决方法是 currying,将单个泛型函数调用拆分为多个泛型函数调用,以便您可以手动指定第一个泛型函数调用的类型参数,然后让编译器推断第二个泛型函数调用的类型参数。这意味着我们需要将上面的
foo()
重构为
declare const foo: <T, >() => <U extends string>(u: U) => F<T, U>;
然后我们可以这样调用该函数:
declare const foo: <T, >() => <U extends string>(u: U) => F<T, U>;
const foobarA = foo<string>()("bar");
// const foobarA: F<string, "bar">
必须添加额外的函数调用有点烦人,但从语法上来说,至少它只是另一对括号,并且它准确地为您提供了您正在寻找的行为。
另一种解决方法是添加与您要手动指定的类型参数相对应的虚拟参数,然后传入该类型的值,以便编译器可以成功推断它。看起来像这样:
declare const foo: <T, U extends string>(dummyT: T, u: U) => F<T, U>
然后我们可以这样调用该函数:
const foobarA = foo("someString", "bar");
// const foobarA: F<string, "bar">
为
"someString"
传入的值 dummyT
几乎肯定会被函数实现忽略,但它允许编译器为 string
推断 T
。如果调用者没有方便的类型值,他们仍然可以通过断言使用虚拟版本,随机的东西属于该类型:
const foobarB = foo(null! as string, "bar");
// const foobarB: F<string, "bar">
这里我使用了
null!
,其值为 null
并且通过后缀 null
断言它是非
!
,我们得到了 never
的不可能的“非空 null”
type 可以分配给任何其他类型。像 null!
这样令人费解的东西的主要好处是,只需要五次击键就可以产生不可能的 never
类型的值,可以断言 as string
而不会被编译器抱怨。
两个版本都可以使用;我倾向于赞成柯里化,除非用例使调用者可以轻松找到并传递虚拟参数的适当参数。
以下内容将真正“工作”——它将编译。不知道能不能真正达到你想要的效果
const foo = <S,T extends string = string>(type:T)=>{
return {
[type]: (arg: S)=>console.log(arg)
}
}