Typescript index signature and methods
Any properties or methods declared in the class or interface with index signature must have type compatible with what you have in the index. That's why adding Function
to the index signature helps.
The reason is explained in the documentation:
While string index signatures are a powerful way to describe the “dictionary” pattern, they also enforce that all properties match their return type. This is because a string index declares that
obj.property
is also available asobj["property"]
. In the following example, name’s type does not match the string index’s type, and the type-checker gives an error:
interface NumberDictionary {
[index: string]: number;
length: number; // ok, length is a number
name: string; // error, the type of 'name' is not a subtype of the indexer
}
Adding any
to the indexer signature can be considered a bad practice,
because any
suppress typechecking and any value obtained in any way from any
also has any
type unless explicitly declared otherwise, so having any
increases chances that you have type errors which are not reported by the compiler.
Adding Function
to the type is better, because it correctly describes the actual data contained in the class - when you use indexed access to get a value like this
const key = 'greeting';
const value = this[key];
you might get a function as a value if key
happens to be equal to 'greet'
. Also, when you assign string value to greet
:
this['greet'] = 'hi!';
the method will be overwritten with a string value and you won't be able to call it any more.
Considering all that, it's better to keep dictionary with index signature in separate property of the class, not in the class itself. Something like this could work:
class Greeter {
data: { [key: string]: string | number[] } = {};
get greeting(): string { return this.data.greeting.toString() }
set greeting(value: string) { this.data.greeting = value };
constructor(message: string) {
this.greeting = message;
}
greet(): string {
return "Hello, " + this.greeting;
}
}