【问题标题】:Map array of functions preserving parameter types. Can variadic tuple types from TypeScript v4 improve this?映射保留参数类型的函数数组。 TypeScript v4 中的可变元组类型可以改善这一点吗?
【发布时间】:2020-08-24 20:13:14
【问题描述】:

最初,我想键入redux'mapDispatchToProps-like 函数,但在处理函数数组(动作创建者)作为参数时遇到了困难。有一个 hacky 但有效的例子。我想知道它是否可以改进。

问题是返回类型不保留每个项目的类型,它被泛化为联合。

更新:问题在于在结果数组中保留每个函数的参数类型。

简而言之,下面的代码应该没有错误:

type F = (...x: any[]) => any
type Wrap<T extends F> = (...x: Parameters<T>) => void

const wrap = (fn: any) => (...a: any) => {fn(...a)}

// currently working solution
// the problem is K being too wide (number | string | symbol) for array index thus silenced
// // @ts-expect-error
// function main<Fs extends readonly F[]>(fs: Fs): {[K in keyof Fs]: Wrap<Fs[K]>}

// TODO: desired solution but not finished: every item in `fs` should be wrapped with `Wrap`
function main<Fs extends readonly F[]>(fs: Fs): [...Fs]

function main(fs: any) {return fs.map(wrap)}

const n = (x: number) => x
const s = (x: string) => x
const fs = main([n, s] as const)

// TEST PARAMETERS TYPES
fs[0](1)
fs[1]('1')
// @ts-expect-error
fs[0]('1')
// @ts-expect-error
fs[1](1)

// TEST RETURN TYPES
const _1: void = fs[0](1)
const _2: void = fs[1]('1')
// @ts-expect-error
const _3: number = fs[0](1)
// @ts-expect-error
const _4: string = fs[1]('1')

TypeScript playground

P.S: an open (as for 25-aug-2020) github issue 与我的解决方案 #1 的问题有关,所以它不是关于可变元组类型,而是关于 keyof ArrayType 对于数组索引类型来说太宽了

【问题讨论】:

    标签: typescript


    【解决方案1】:

    TypeScript 4.0 确实可以表示variadic tuple types,因此您可以注释您的main 函数以表明它的作用;也许是这样的:

    const main = <F extends any[]>(f: F): [...F] => [...f];
    

    (如果您想将其设为 F extends Function[] 或某些应该也可以工作的特定函数子类型,但对于此示例,我只是使用 any 而不是函数类型。)现在您上面的示例就像您一样想要:

    const n = (x: number) => x
    const s = (x: string) => x
    const [n2, s2] = main([n, s])
    n2(1) // okay
    s2('1') // okay
    n2('1') // error
    s2(1) // error
    

    Playground link.

    但是对于 TypeScript 3.9 及以下版本,asserting 可能会得到类似这样的行为,即返回类型与输入类型相同,这与您正在做的事情足够接近并且行为相同(两者之间的区别[...F]F 足够小,在大多数情况下不用担心):

    const main = <F extends any[]>(f: F) => [...f] as F;
    

    Playground link


    请注意,有时编译器不会在没有提示的情况下推断出您想要的元组类型;通过写 const [n2, s2] = main([n, s]); 你暗示你想要元组,所以你得到元组。

    否则,const n2s2 = main([n, s]) 将只是一个无序数组类型,其元素属于联合类型。您可以通过使F 类型参数在其约束中包含元组类型来在main 的类型签名中给出提示:

    const main = <F extends any[] | []>(f: F) => [...f] as F;
    

    (请参阅microsoft/TypeScript#27179 了解这个想法)这使得这项工作:

    const n2s2 = main([n, s]);
    // const n2s2: [(x: number) => number, (x: string) => string]
    

    Playground link


    因此,这些技术的某种组合应该有望解决您遇到的问题。希望有帮助;祝你好运!

    【讨论】:

    • 感谢您的详细回答,但我更新了我的问题并提供了更多细节:还有一个映射器功能
    • 另外,我不知道元组声明的区别 - 似乎是 const n2s2 = main([n, s])const n2s2 = main([n, s] as const) 之间的中间方式
    【解决方案2】:

    您实际上根本不需要 TS4 可变元组。只是映射(元组)类型。以下适用于 TypeScript 3.5(尽管 // @ts-expect-error 是 3.9 的功能):

    function main<Fs extends readonly F[]>(fs: Fs):
        { [K in keyof Fs]: Fs[K] extends F ? Wrap<Fs[K]> : never }
    

    Playground Link


    更新:在数组(而不是元组)类型上,上面将返回一个数组类型,其中各个索引不再是已知的。一旦元组索引丢失,就无法恢复此信息。您可以使用as const 来防止元组信息的初始丢失:

    const test3 = [
        console.log,
        (x: number) => x,
    ] as const;
    const fs3 = main(test3);
    // const fs3: readonly [Wrap<(...data: any[]) => void>, Wrap<(x: number) => number>]
    

    如果您想确保不会丢失索引,可以拒绝数组类型并只允许使用一些技巧的元组:

    function main<Fs extends readonly F[]>(
        fs: number extends Fs['length'] ? never : Fs):
        { [K in keyof Fs]: Fs[K] extends F ? Wrap<Fs[K]> : never }
    

    (编辑:K in Exclude&lt;keyof Fs, keyof []&gt; -> K in keyof Fs 就像上面一样,不知道我为什么会这样)
    和上面一样,所有这些都可以在 TS3 中使用。

    Playground Link

    【讨论】:

    • 感谢您的输入,但事实并非如此,因为即使在您添加的 fs2 示例类型中也是 const fs2: Wrap&lt;(...data: any[]) =&gt; void&gt;[],即类型已损坏
    • @ts-expect-error 在这里注释预期的错误,如果下面的行中没有错误,那么该注释本身就会成为错误。所以,你还是要在 TS@3.9+ 上检查一下
    • 您是说只允许可以推断元组类型的输入,并且如果只能推断列表类型则创建错误?
    • 是的,我想传入一个函数列表 A 并返回一个包装函数 B 的列表,每个函数都具有与 A 中完全相同的参数类型。未扩展联合类型,这是 TS 的默认类型推断行为.
    • 似乎Exclude&lt;keyof Fs, keyof []&gt; 正在删除@@iterator,已删除:tsplay.dev/1WGRVm
    【解决方案3】:

    您需要让 typescript 推断元组类型。阅读Variadric Tuples,看来您需要将泛型类型参数限制为类型any[],然后返回该泛型参数的扩展:

    type Main = <T extends any[]>(fns: T) => [...T]
    
    const main: Main = (fns) => [...fns]
    

    现在其余的工作就像您期望的那样。

    Playground

    【讨论】:

    • 感谢您的建议,我更新了我的问题并提供了更多细节:还有一个映射器功能
    猜你喜欢
    • 1970-01-01
    • 2012-09-23
    • 2018-02-05
    • 1970-01-01
    • 2021-04-20
    • 1970-01-01
    • 2019-04-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多