【问题标题】:How to create a type that extract all methods from a class in typescript?如何创建一个从打字稿中的类中提取所有方法的类型?
【发布时间】:2022-01-22 09:55:57
【问题描述】:

假设我有一堂课Foo

class Foo {
  bar(){
     // anything
  }

  baz() {
    // anything
  }
}

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

例如

type FooMethod = ExtractMethods<Foo> // => { bar(): void; baz(): void; } 

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

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

【问题讨论】:

  • 如果您的代码示例显示了一些用例和边缘情况,这会有所帮助,因为现在type ExtractMethods&lt;T&gt; = T 将适用于您的示例。无论如何,this 对你有用吗?据我所知,您无法将方法(存在于类原型中)与函数值属性(往往存在于实例中)区分开来,因此用例真正决定了您想要的是简单/困难/不可能。跨度>
  • @jcalz 听起来很完美。我不知道不可能将方法与函数属性区分开来。但对我来说,没关系
  • @jcalz 如果你愿意,请将其添加为答案
  • 我想知道是否有一种偷偷摸摸的方法来区分方法和函数值属性,使用方法参数的二元性?我想这不可靠,因为并非所有方法/函数都有参数。
  • @kaya3 是的,如果你有方法参数,你可能会以某种方式进行双方差检查,但据我所知,没有很好的方法来概括这一点。据我所知,映射类型总是将方法转换为 func-props。不过,我很想被证明是错误的!

标签: typescript typescript-generics


【解决方案1】:

让我们编写一个类型函数PickMatching&lt;T, V&gt;,它接受一个对象类型T 和一个属性值类型V,并仅将T 的属性类型可分配给V 的那些属性计算为另一个对象类型。如果我们使用key remapping in mapped types,这很简单:

type PickMatching<T, V> =
    { [K in keyof T as T[K] extends V ? K : never]: T[K] }

通过将键映射到never,我们实质上是从映射类型中省略了该属性。然后我们可以通过从T 中选择所有函数值属性来表达ExtractMethods&lt;T&gt;

type ExtractMethods<T> = PickMatching<T, Function>;

这会产生以下结果:

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

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

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

这确实是实现ExtractMethods 的最佳方法。


另一方面,如果您只想为Foo 之类的特定 类执行此操作,并且可以从类型级别下拉到值级别,则可以使用以下事实:编译器理解spreading 一个类的实例到一个新对象只会给你实例属性:

const spreadFoo = { ... new Foo() };
/* const spreadFoo: {
  notAMethod: number;
  funcProp: () => number;
} */

那个你可以计算FooMethod(假设你没有任何非方法原型成员):

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

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

Playground link to code

【讨论】:

    【解决方案2】:

    基于this article,我想出了以下解决方案(playground):

    class Test {
      foo() {}
      bar() {}
      zar = 3;
    }
    
    type SubType<Base, Condition> = Pick<Base, {
        [Key in keyof Base]: Base[Key] extends Condition ? Key : never
    }[keyof Base]>;
    
    type MethodsOnly<T> = SubType<T, () => unknown>;
    
    type T = Pick<Test, keyof MethodsOnly<Test>>
    
    const test = {} as T;
    
    test.bar; // legal
    test.foo; // legal
    test.zar; // Property 'zar' does not exist on type 'T'.(2339)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-08-24
      • 2018-08-30
      • 2019-03-27
      • 2021-03-13
      • 2016-03-04
      • 2022-11-13
      • 2020-11-03
      相关资源
      最近更新 更多