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

Posted

技术标签:

【中文标题】如何创建一个从打字稿中的类中提取所有方法的类型?【英文标题】: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。不过,我很想被证明是错误的! 【参考方案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)

【讨论】:

以上是关于如何创建一个从打字稿中的类中提取所有方法的类型?的主要内容,如果未能解决你的问题,请参考以下文章

在打字稿中的类中创建具有未知参数的方法

我们可以在打字稿中访问另一个类中的私有变量吗

我们可以在打字稿中访问另一个类中的私有变量吗

从打字稿中的对象获取特定类型的所有键[重复]

如何理解打字稿中的“属性'名称'在'用户'类型中是私有的”

如何将方法的泛型类型限制为打字稿中的对象?