Let's create a custom function called ExtractMatchingProps<T, V>
that receives an object type T
and a property value type V
, returning a new object type with only those properties of T
whose types match or are assignable to V
. We can achieve this easily by utilizing key remapping in mapped types:
type ExtractMatchingProps<T, V> =
{ [K in keyof T as T[K] extends V ? K : never]: T[K] }
When we map a key to never
, we essentially exclude that property from the resulting type. Subsequently, by calling ExtractMethods<T>
and passing it an object of type T
, we effectively extract all method-type properties from T
:
type ExtractMethods<T> = ExtractMatchingProps<T, Function>;;
The output for this operation would look like:
class Foo {
bar() { }
baz() { }
notAMethod = 123;
funcProp = () => 10;
}
type FooMethod = ExtractMethods<Foo>
/* type FooMethod = {
bar: () => void;
baz: () => void;
funcProp: () => number;
} */
As observed, the property notAMethod
with a type of number
is excluded from FooMethod
, while methods like bar()
and baz()
are retained as intended. Essentially, the function-typed property funcProp
is also preserved in FooMethod
. It's important to note here that the type system may not reliably differentiate between a method and a function-valued property, treating both similarly due to them being present on either the instance directly or the class prototype.
This illustrates the best way to implement ExtractMethods
in a generic manner.
Alternatively, if you wish to target this functionality specifically for a particular class such as Foo
and don't mind moving from the type level to the value level, you can leverage the fact that spreading an instance of a class into a new object only includes instance properties:
const spreadFoo = { ... new Foo() };
/* const spreadFoo: {
notAMethod: number;
funcProp: () => number;
} */
From this object, you could then derive FooMethod
(assuming there are no non-method prototype members):
type FooMethod = Omit<Foo, keyof typeof spreadFoo>;
/* type FooMethod = {
bar: () => void;
baz: () => void;
} */
However, the practicality of this approach largely depends on your specific use cases.
Link to Playground for code demonstration