【问题标题】:how to apply a function type with generic to a function如何将具有泛型的函数类型应用于函数
【发布时间】:2020-02-07 19:01:48
【问题描述】:

我有以下类型:

type AllRoutes = {
  Home: undefined
  Page: { id: string }
}

type NavFunction<RouteName extends keyof AllRoutes> = (
  ...args: AllRoutes[RouteName] extends undefined
    ? [RouteName]
    : [RouteName, AllRoutes[RouteName]]
) => void

如果我将这种类型直接放在这样的函数上,则可以按预期工作:

export function navigate<RouteName extends keyof AllRoutes>(
  ...args: AllRoutes[RouteName] extends undefined
    ? [RouteName]
    : [RouteName, AllRoutes[RouteName]]
) {
  ...
}

navigate('Home') // works!
navigate('Page', {id: 1}) // works!
navigate('Page') // Expected 2 args

我遇到的问题是我想将此类型应用于多个功能(每个功能用于不同的平台)。我似乎不知道如何将第一种类型应用于函数并让它像我刚刚发布的“直接”示例一样工作。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    您可以为args 部分创建帮助器类型。也许还为keyof AllRoutes创建一个类型?:

    type RouteNames = keyof AllRoutes;
    type GetArgs<RouteName extends RouteNames> = AllRoutes[RouteName] extends undefined
        ? [RouteName]
        : [RouteName, AllRoutes[RouteName]];
    
    export function navigate<RouteName extends RouteNames>(
      ...args: GetArgs<RouteName>
    ) {
      ...
    }
    

    【讨论】:

    • 太棒了!我需要进行以下编辑,但它似乎有效:GetArgs&lt;RouteName&gt; -> GetArgs &lt;RouteName extends RouteNames&gt;
    【解决方案2】:

    首先,请注意NavFunction 类型,虽然与navigate 的函数类型相似,但实际上并不相同。相反,它应该将泛型部分附加到函数,而不是类型名称,如下所示:

    // Note the generic part is now attached to the start of the function
    // signature, rather than as a part of the NavFunction type name.
    type NavFunction = <RouteName extends keyof AllRoutes>(
      ...args: AllRoutes[RouteName] extends undefined
        ? [RouteName]
        : [RouteName, AllRoutes[RouteName]]
    ) => void
    

    不同之处在于,在原版中,NavFunction 要求您预先指定期望的参数,而在修订版中,将在使用函数时进行推断。 p>

    这样,您可以使用 函数表达式 而不是 函数声明 来附加类型:

    const navigate: NavFunction = function(...args) {
      // ...
    }
    
    navigate('Home') // works!
    navigate('Page', {id: "1"}) // works!
    navigate('Page') // Expected 2 args
    

    但请注意,以这种方式执行此操作,navigate 函数将不再被提升。 :(

    【讨论】:

      【解决方案3】:

      这不是一个直接的答案,而是一个改变问题的提议。

      这些函数的预期用法很好,但需要一个不容易维护的类型。使用具有可区分联合类型的整个对象参数怎么样?

      // Routes discriminated by name
      type HomeRoute   = { name: 'Home' };
      type PageRoute   = { name: 'Page'; id: number };
      type SearchRoute = { name: 'Search'; text: string; limit?: number };
      
      type Route = HomeRoute | PageRoute | SearchRoute;
      
      function navigateV1(route: Route): void { /*...*/ }
      
      navigateV1({ name: 'Home' })
      navigateV1({ name: 'Page', id: 1 })
      navigateV1({ name: 'Page' }) // Got expected error "Property 'id' is missing..."
      navigateV1({ name: 'Search', text: 'A*' })
      

      用法只是有点冗长,但也给出了更详细的错误。

      --

      顺便说一句,使用上述区分联合类型,为了坚持所需的用法,我们还需要复杂的实用程序类型:

      // V2 (more complex to maintain) : arguments as deconstructed object
      type RouteName = Route['name']; // "Home" | "Page" | "Search"
      
      type RouteArgs<TRouteName extends RouteName,
                     TRoute = Extract<Route, { name: TRouteName }>,
                     TRest  = Omit<TRoute, 'name'>> =
        {} extends TRest
        ? [TRouteName]
        : [TRouteName, TRest];
      
      type TestHomeRouteArgs   = RouteArgs<'Home'>;   // ["Home"]
      type TestPageRouteArgs   = RouteArgs<'Page'>;   // ["Page", Pick<PageRoute, "id">]
      type TestSearchRouteArgs = RouteArgs<'Search'>; // ["Search", Pick<PageRoute, "text" | "limit">]
      
      function navigateV2<TRouteName extends RouteName>(...args: RouteArgs<TRouteName>): void { /*...*/ }
      
      navigateV2('Home')
      navigateV2('Page', { id: 1 })
      navigateV2('Page') // Got expected error but less precise: "Expected 2 arguments but got 1"
      navigateV2('Search', { text: 'A*' })
      
      // ----
      // V3 : even more complex but output types more readable
      type Prettify<T> = T extends infer Tbis ? { [K in keyof Tbis]: Tbis[K] } : never;
      
      type PrettyRouteArgs<TRouteName extends RouteName,
                     TRoute = Extract<Route, { name: TRouteName }>,
                     TRest  = Omit<TRoute, 'name'>> =
        {} extends TRest
        ? [TRouteName]
        : [TRouteName, Prettify<TRest>];
      
      type TestPrettyHomeRouteArgs   = PrettyRouteArgs<'Home'>;   // ["Home"]
      type TestPrettyPageRouteArgs   = PrettyRouteArgs<'Page'>;   // ["Page", { id: number; }]
      type TestPrettySearchRouteArgs = PrettyRouteArgs<'Search'>; // ["Search", { text: string; limit?: number | undefined; }]
      
      function navigateV3<TRouteName extends RouteName>(...args: PrettyRouteArgs<TRouteName>): void { /*...*/ }
      
      navigateV3('Home')
      navigateV3('Page', { id: 1 })
      navigateV3('Page') // Got same expected error
      navigateV3('Search', { text: 'A*' })
      

      【讨论】:

        猜你喜欢
        • 2010-10-04
        • 1970-01-01
        • 2020-09-24
        • 2016-11-09
        • 1970-01-01
        • 2022-01-02
        • 1970-01-01
        • 2022-01-04
        相关资源
        最近更新 更多