Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript index signature and methods

Tags:

typescript

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.

like image 374
Zaw W. Lwin Avatar asked Mar 07 '19 23:03

Zaw W. Lwin


People also ask

What is an index signature in TypeScript?

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.

Is incompatible with index signature TypeScript?

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.

What is the difference between interface and type in TypeScript?

// 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.

How do you define a key value pair in TypeScript?

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.


1 Answers

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 as obj["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;
    }
}
like image 147
artem Avatar answered Oct 04 '22 12:10

artem