1

I have the following example:

class Parent {
  foo() {
    console.log("foo");
  }
}

class Child1 extends Parent {
  bar() {
    this.foo();
    console.log("bar");
  }
}

class Child2 extends Parent {
  baz() {
    this.foo();
    console.log("baz");
  }
}

const Children = [Child1, Child2];

function someFunc<C>(children: C[]) { // ???
  const _children = {}; // ???
  for (let c of children) {
    const _c = new c();
    _children[c.name] = _c;
  }
  return _children;
}

const instances = someFunc(Children); // ???

instances.Child1.bar();

How can I tell the someFunc method that its going to be expecting an array of Classes of type Children? What do I need to pass as a Generic argument to someFunc?

Mostly so it will know that instances.Child1 has a method called bar()

Runnable: https://codesandbox.io/s/flamboyant-jackson-jnp1e

benhowdle89
  • 36,900
  • 69
  • 202
  • 331
  • This would be simple if you were implementing a shared interface on both C1 and C2 - is this an option? because then you could just pass the interface for the generic type. – Zze Feb 26 '20 at 13:12
  • I could do this. Silly question: How would a shared interface across multiple classes that might inherit from the same class, but implement different method? ie. if Child1 had a "baz" method, but Child2 had a "test" method? – benhowdle89 Feb 26 '20 at 13:14
  • Yeah, that's the caveat where it would not work with interfaces. In the example, it looks like it would as both have `bar()` maybe an update to the question so that child2 is `foo()` or something? – Zze Feb 26 '20 at 13:16

1 Answers1

0

Try C extends new (...args: any[]) => any for the generic type, and InstanceType<C> for instantiated objects present within the _children variable, as below.

type Constructor = new (...args: any[]) => any;

const Children = [Child1, Child2];

function someFunc<C extends Constructor>(children: C[]) {
  const _children: Record<string, InstanceType<C>> = {};
  for (let c of children) {
    const _c = new c();
    _children[c.name] = _c;
  }
  return _children;
}

const instances = someFunc(Children);

instances.Child1; // typed as `Child1 | Child2`
instances.Child2; // also typed as `Child1 | Child2`

instances.Child1.bar();

https://codesandbox.io/s/infallible-poitras-423fc

This is not perfect, but closer to what you're trying to achieve. One minor issue is that you can still access any key from instances (say, instances.SomeUnknownClass, which would mistakenly point to a value of type Child1 | Child2).

Also if you need the children argument to only allow classes Child1 and Child2 (or whatever your Children array holds), the signature of your function should rather look like:

type AllowedClass = typeof Children[number]; // typeof Child1 | typeof Child2

function someFunc<C extends AllowedClass>(children: C[]) {
  const _children: Record<string, InstanceType<C>> = {};
  for (let c of children) {
    const _c = new c();
    _children[c.name] = _c;
  }
  return _children;
}
Crogo
  • 483
  • 4
  • 8