distante
distante

Reputation: 7005

Is it possible to force that all class public methods return the same type with typescript?

I am building some helper classes that operate on or get some specific DOM element types.

So I have for example an HTMLDivElementHelper all public methods should return HTMLDivElement.

Those methods contain at least one params but they can also contain 2.

I am trying to force this with an interface or similar but I can not find how to do it.

If I use class MyHelper implements Record<string, () => HTMLDivElement> Typescript complains that Property 'setValue' of type '(selector: string) => HTMLDivElement' is not assignable to 'string' index type '() => HTMLDivElement'.(2411)

Playground here

Is this possible?

Upvotes: 2

Views: 877

Answers (2)

jcalz
jcalz

Reputation: 329388

If you'd like to allow your class to have members that aren't functions, then you can't use a string index signature, nor can you say implements Record<string, XXX> because the Record<K, V> utility type is the same as an index signature when your key type is string.


Instead, you can come up with a generic type CheckMethods<T> that acts as a constraint on your class. The idea is that you write class MyHelper implements CheckMethods<MyHelper> { /* ... */ }, where CheckMethods<MyHelper> will evaluate to something compatible with MyHelper if and only if all the methods (well, function-valued members) are of the type you want them to be. This sort of self-referential constraint is known as "F-bounded quantification". Here's a possible implementation:

type CheckMethods<T> = { [K in keyof T]: T[K] extends Function ?
  ((selector: string, options: any) => HTMLDivElement) : T[K] }

Here CheckMethods<T> is a mapped type where each property key K from T is mapped to a new property type. The old property type T[K] is checked with a conditional type to see if it's a Function or not. If it's not, we just map it to itself, T[K], which will always be true. If it is a function, then we map it to a specific function type (selector: string, options: any) => HTMLDivElement, which will work with any function of up to two parameters of the same type (see the documentation for function compatiblity for more information.

Let's see it in action:

class MyHelper implements CheckMethods<MyHelper> {

  nonMethod = 123; // okay

  setValue(selector: string): HTMLDivElement { // okay
    return document.createElement('div');
  }

  setTest(selector: string, options: { options: string }): HTMLDivElement { // okay
    return document.createElement('div');
  }

  badParameter(selector: number): HTMLDivElement { // error!
     return document.createElement("div");
  } 

  badReturn(selector: string) { return 3 } // error!

}

Looks good!

Playground link to code

Upvotes: 5

Jurijs Kovzels
Jurijs Kovzels

Reputation: 6240

Try this:

It compiles but I'm not sure that this is what you are looking for.

type MethodSignature = (selector: string, options?:{options: string}) => HTMLDivElement;

class MyHelper implements Record<string, MethodSignature> {
  [k: string]: MethodSignature;

  setValue(selector: string): HTMLDivElement {
    return document.createElement('div');
  }

  setTest(selector: string, options?: {options: string}): HTMLDivElement {
    return document.createElement('div');
  }
}

const myHelper = new MyHelper();

Upvotes: 0

Related Questions