代码之家  ›  专栏  ›  技术社区  ›  Marvin

缩小类型,不包含重复字符串

  •  1
  • Marvin  · 技术社区  · 4 月前

    我想编写一个使用服务的通用函数。基于此服务,应提供一组可用的操作。根据所选择的动作,应提供所需的参数。

    下面是一个最小的例子:

    class BaseService {
      declare baseAction: (base: string) => void // Must not be shown as an `action` option
    }
    
    class MyService extends BaseService {
      declare fooAction: (foo1: number, foo2: string) => void
      declare barAction: (bar: string) => void
    }
    
    class MyOtherService extends BaseService {
      declare otherAction: (other: number) => void
    }
    
    function invokeAction<TService extends BaseService>(
      action: Exclude<keyof TService, keyof BaseService> & string,
      ...args: Parameters<Extract<TService[typeof action], (...args: any) => any>>
    ) {}
    
    invokeAction<MyService>("fooAction", 1, "foo")  // This is supposed to work ✅ 
    invokeAction<MyService>("barAction", "bar")     // This is supposed to work ✅ 
    invokeAction<MyService>("barAction", 1, "foo")  // This is NOT supposed to work ❌
    
    invokeAction<MyOtherService>("otherAction", 1)  // This is supposed to work ✅ 
    invokeAction<MyOtherService>("barAction", "bar) // This is NOT supposed to work ✅
    

    有了这个,the action 根据服务正确选择选项。但是 ...args 包括中可用的所有操作的参数 TService 。我能否以某种方式缩小范围,而不需要像这样键入两次动作:

    function invokeActionWorkaround<
      TService extends BaseService,
      TAction extends Exclude<keyof TService, keyof BaseService> & string
    >(
      action: TAction,
      ...args: Parameters<Extract<TService[TAction], (...args: any) => any>>
    ) {}
    

    在这里,我必须指定两次操作名称(没有IntelliSense),但操作的参数是正确确定的。

    1 回复  |  直到 4 月前
        1
  •  1
  •   Alexander Nenashev    4 月前

    您可以使用一个工厂函数,该函数返回一个调用函数,该调用函数使用所提供类型的方法:

    Playground

    type MethodKeys<T extends object> = keyof {[K in keyof T as T[K] extends (...args: any) => any ? K : never]: any};
    
    function invoker<TService extends BaseService>(){
      return function <K extends MethodKeys<TService>>(
      action: K,
      ...args: TService[K] extends (...args: any) => any ? Parameters<TService[K]> : never
    ) {}
    
    }
    const invokeAction = invoker<MyService>();
    invokeAction("fooAction", 1, "foo")  // This is supposed to work ✅ 
    invokeAction("barAction", "bar")     // This is supposed to work ✅ 
    invokeAction("barAction", 1, "foo")  // This is NOT supposed to work ❌
    
    const invokeAction2 = invoker<MyOtherService>();
    invokeAction2("otherAction", 1)  // This is supposed to work ✅ 
    invokeAction2("barAction", "bar") // This is NOT supposed to work âœ