如何创建一个从打字稿中的类中提取所有方法的类型?

Ven*_*sky 8 typescript typescript-generics

假设我有课Foo

class Foo {
  bar(){
     // anything
  }

  baz() {
    // anything
  }
}
Run Code Online (Sandbox Code Playgroud)

如何创建一个类型ExtractMethods,它将接收一个类并返回一个接口或带有类方法的类型?

例如

type FooMethod = ExtractMethods<Foo> // => { bar(): void; baz(): void; } 
Run Code Online (Sandbox Code Playgroud)

我知道对这个问题的一个常见评论是“为什么不直接创建一个界面Foo并使用您创建的界面呢?”

但在我要解决的情况下,Foo不是我创建的类,而是第三方类。我可以为 创build一个接口Foo,但它永远不会完美地反映方法,万一类将来因更新而发生变化,我还需要更新接口,这是我试图避免的事情。

jca*_*alz 12

让我们编写一个类型函数PickMatching<T, V>,它接受一个对象类型T和一个属性值类型,并仅将其属性类型可分配给 的V那些属性计算为另一个对象类型。如果我们在映射类型中使用键重新映射,这很简单:TV

type PickMatching<T, V> =
    { [K in keyof T as T[K] extends V ? K : never]: T[K] }
Run Code Online (Sandbox Code Playgroud)

通过将键映射到,never我们实质上省略了映射类型中的属性。然后我们可以ExtractMethods<T>通过从 中选取所有函数值属性来表达T

type ExtractMethods<T> = PickMatching<T, Function>;
Run Code Online (Sandbox Code Playgroud)

这会产生以下结果:

class Foo {
    bar() { }
    baz() { }
    notAMethod = 123;
    funcProp = () => 10;
}

type FooMethod = ExtractMethods<Foo>
/* type FooMethod = {
    bar: () => void;
    baz: () => void;
    funcProp: () => number;
} */
Run Code Online (Sandbox Code Playgroud)

如您所见, -typednumbernotAMethod存在于FooMethod. 方法bar()和会根据需要baz()出现。FooMethod重要的是,函数类型funcProp存在于. 类型系统无法可靠地区分方法函数值属性。主要区别在于成员是直接存在于实例上还是存在于类原型上,但类型系统将类实例和类原型视为同一类型。FooMethod

这确实是您可以做的最好的ExtractMethods一般实施。


另一方面,如果您只想对特定类执行此操作,例如Fooand 可以从类型级别下降到值级别,则可以利用编译器理解将类的实例传播到新对象中的事实只会给你实例属性:

const spreadFoo = { ... new Foo() };
/* const spreadFoo: {
  notAMethod: number;
  funcProp: () => number;
} */
Run Code Online (Sandbox Code Playgroud)

由此可以计算FooMethod(假设您没有任何非方法原型成员):

type FooMethod = Omit<Foo, keyof typeof spreadFoo>;
/* type FooMethod = {
  bar: () => void;
  baz: () => void;
} */
Run Code Online (Sandbox Code Playgroud)

但这取决于您的用例,这种方法是否可行。

Playground 代码链接