James
James

Reputation: 82096

Generic return type for index signature in TypeScript

Here's the problem

interface Prop<T> {
   value: T;
}

class Property<T> implements Prop<T> {
   value: T;
   constructor(value: T) {
      this.value = value;
   }
}

class Node {
   this.props: { [index: string]: Prop<T> } // how do you define T?
}

T can't be defined at class level here as needs as the intended use would be along the lines of

const strProp = node.props<string>['strProp'];
const numProp = node.props<number>['numProp'];

In other words, a Node can have various property types attached to it.

There doesn't appear to be anything in the docs about this (or perhaps I'm just not seeing it). Basically what I'm really looking for here is a generic indexer, for example:

this.props: <T>{ [index: string]: Prop<T> }

Does it exist?

Disclaimer - I'm not looking for workarounds, I understand there are ways around this but I'd like to know whether or not the support is there (I couldn't see any proposals or outstanding issues on the repo for this). There were some similar issues but nothing specific to this particular scenario.

Upvotes: 0

Views: 1494

Answers (1)

jcalz
jcalz

Reputation: 328503

No, in TypeScript only functions (including methods) and types (classes, interfaces, type aliases) can be generic.


Additionally, it doesn't look like what you're asking for actually makes much sense. To see why, let's look at one of those workarounds you're not necessarily interested in:

abstract class Node {
  abstract propsGetter<T>(index: string): Prop<T>;
}

Here we define a getter method which takes a type parameter T and a string parameter and returns a value of type Prop<T>. This is more or less the equivalent of an indexed property. Notice that it is the caller, not the implementer of this method that specifies both the type T and index.

You would, I suppose, call it like this:

declare const node: Node;
const strProp = node.propsGetter<string>('strProp');
const numProp = node.propsGetter<number>('numProp');

But wait, nothing stops you from calling it like this:

const whatProp = node.propsGetter<string>('numProp'); // no error

If you were expecting that the compiler would somehow know that the 'numProp' parameter would return a Prop<number> and that there would be an error, the compiler would disappoint you. The signature of propsGetter promises that it will return a Prop<T> for whatever value of T the caller wants, no matter what the index parameter is.

Unless you describe for the compiler some relationship between the type of index (presumably some collection of string literals) and the type of T, there's no type safety here. The type parameter does nothing for you. You might as well remove the type parameter and just do something like:

abstract class Node {
  abstract propsGetter(index: string): Prop<{}>;
}

which returns a Prop<{}> value you have to type check or assert:

const strProp = node.propsGetter('strProp') as Prop<string>; // okay
const numProp = node.propsGetter('numProp') as Prop<number>; // okay

This is just as non-type-safe as the above but at least it is explicit about it.

const whatProp = node.propsGetter('numProp') as Prop<string>; // still no error but it's your responsibility

And then since we don't need the generic parameter you can indeed use an indexer:

class Node {
   props: { [index: string]: Prop<{}> } 
}

Recall,

Unless you describe for the compiler some relationship between the type of index (presumably some collection of string literals) and the type of T, there's no type safety here.

Do you have some kind of way to tell the compiler which property keys should return which property types? If so, this is looking like you don't actually want a pure string-indexed type but a standard object of the sort:

abstract class Node {
  props: {
    strProp: Prop<string>,
    numProp: Prop<number>,
    // ... others ...
    [otherKeys: string]: Prop<{}> // default
  }
}

Maybe one of those meets your use case... not that you care, as your disclaimer disclaims. In the case that you don't care, please ignore everything after my first sentence.

Hope that helps. Good luck!

Upvotes: 2

Related Questions