TypeScript Index Signatures
Introduction
An object in JavaScript can be accessed with a string to hold a reference to any other JavaScript object.
let foo: any = {}
foo["Hello"] = "World"
console.log(foo["Hello"]) // World
class Foo {
constructor(public message: string) {}
log() {
console.log(this.message)
}
}
let foo: any = {}
foo["Hello"] = new Foo("World")
foo["Hello"].log() // World
let obj = {
toString() {
console.log("toString called")
return "Hello"
},
}
let foo: any = {}
foo[obj] = "World" // toString called
console.log(foo[obj]) // toString called, World
console.log(foo["Hello"]) // World
Syntax
Declaring an index signature
let foo: { [index: string]: { message: string } } = {}
/**
* Must store stuff that conforms to the structure
*/
/** Ok */
foo["a"] = { message: "some message" }
/** Error: must contain a `message` of type string. You have a typo in `message` */
foo["a"] = { messages: "some message" }
/**
* Stuff that is read is also type checked
*/
/** Ok */
foo["a"].message
/** Error: messages does not exist. You have a typo in `message` */
foo["a"].messages
Interfaces
As soon as you have a string index signature, all explicit members must also conform to that index signature. A few Interface examples:
interface Foo {
x: number
}
let foo: Foo = { x: 1, y: 2 }
// Directly
foo["x"] // number
// Indirectly
let x = "x"
foo[x] // number
String literals
String Literals can also be used in the declaration:
type Index = "a" | "b" | "c"
type FromIndex = { [k in Index]?: number }
const good: FromIndex = { b: 1, c: 2 }
// Error:
// Type '{ b: number; c: number; d: number; }' is not assignable to type 'FromIndex'.
// Object literal may only specify known properties, and 'd' does not exist in type 'FromIndex'.
const bad: FromIndex = { b: 1, c: 2, d: 3 }
Generics
Generics can also be used:
type FromSomeIndex<K extends string> = { [key in K]: number }