Why does the following code give ts(2411)
error?
class Greeter {
[key: string]: string | number[];
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet(): string {
return "Hello, " + this.greeting;
}
}
The error is at greet()
which says type '() => string'
not assignable to type 'string | number[]'
.
The error goes away if I add object
to index signature. Why is that?
Also for index signature, is it using any
bad practice?
Edit: I also added interface Function
to the signature. It works as well. Still, the question is why.
Index signature is used to represent the type of object/dictionary when the values of the object are of consistent types. Syntax: { [key: KeyType] : ValueType } Assume that we have a theme object which allows us to configure the color properties that can be used across the application.
The error "Property is incompatible with index signature" occurs when a property is not compatible with the specified type of the index signature. To solve the error, change the type of the property or use a union to update the type in the index signature.
// One major difference between type aliases vs interfaces are that interfaces are open and type aliases are closed. This means you can extend an interface by declaring it a second time. // In the other case a type cannot be changed outside of its declaration.
Use an index signature to define a key-value pair in TypeScript, e.g. const employee: { [key: string]: string | number } = {} . An index signature is used when we don't know all the names of a type's keys ahead of time, but we know the shape of their values.
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With