Index Signatures in TypeScript
source link: https://dmitripavlutin.com/typescript-index-signatures/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Index Signatures in TypeScript
When making my first steps in TypeScript, I was surprised that the following code triggers a type error:
const dictionary = {
'one': 1,
'two': 2,
'three': 3
for (const name in dictionary) {
const myNumber = dictionary[name];
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ one: number; two: number; three: number; }'.
No index signature with a parameter of type 'string' was found on type '{ one: number; two: number; three: number; }'.Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ one: number; two: number; three: number; }'.
No index signature with a parameter of type 'string' was found on type '{ one: number; two: number; three: number; }'.
What I want is to loop through the dictionary
properties and get their values. TypeScript, however, triggers a type error on the expression dictionary[name]
, requiring an index signature on the type of dictionary
.
That’s when I met index signatures.
Let’s find what are TypeScript index signatures and when they’re needed.
1. Why index signature
Let’s continue exploring the type error from the previous code sample. Why TypeScript doesn’t like dictionary[name]
?
First, let’s see what type is the dictionary
variable — an object type with fixed property names:
dictionary;
const dictionary: {
one: number;
two: number;
three: number;
}
Second, let’s see what name
variable is? It’s a string
type.
Here’s the problem: since the dictionary has a predefined set of properties, dictionary[name]
tries to read a property that may not exist on dictionary
, because name
can be any string
value. That’s what TypeScript is complaining about.
How to access properties using a key of string
type?
That’s where the index signature can help. Let’s annotate the dictionary
variable with an index signature:
interface NumberByName {
[name: string]: number
const dictionary: NumberByName = {
'one': 1,
'two': 2,
'three': 3
for (const name in dictionary) {
const myNumber = dictionary[name]; // Good!
const myNumber: number
Having the dictionary
annotated with an index signature where the key is string
and value is a number
type, the TypeScript doesn’t complain about dictionary[name]
.
TypeScript understands that if you use a property accessor with a string
type, the resulted value is going to be a number
:
type ValueType = (typeof dictionary)[string];
type ValueType = number
The idea of the index signatures is to map key type to value type. That allows you to type objects of unknown structure when the only thing you know is the key and value types.
2. Index signature syntax
The syntax of an index signature is pretty simple and looks similar to the syntax of a property, but with one difference. Instead of the property name, you simply write the type of the key
inside the square brackets: { [key: KeyType]: ValueType }
.
Here are a few examples of index signatures.
The string
type is the key and value:
interface StringByString {
[key: string]: string;
const heroesInBooks: StringByString = {
'Gunslinger': 'The Dark Tower',
'Jack Torrance': 'The Shining'
The string
type is the key, the value can be a string
, number
, or boolean
:
interface Options {
[key: string]: string | number | boolean;
timeout: number;
const options: Options = {
timeout: 1000,
timeoutMessage: 'The request timed out!',
isFileUpload: false
Options
interface also has a field timeout
, which works fine near the index signature.
The key of the index signature can only be a string
, number
, or symbol
. Other types are not allowed:
interface OopsDictionary {
[key: boolean]: string;
An index signature parameter type must be 'string', 'number', 'symbol', or a template literal type.An index signature parameter type must be 'string', 'number', 'symbol', or a template literal type.
3. Index signature caveats
The index signatures in TypeScript have a few caveats you should be aware of.
3.1 Non-existing properties
What would happen if you try to access a non-existing property of an object whose index signature is { [key: string]: string }
?
As expected, TypeScript infers the type of the value to string
. But if you check the runetime value — it’s undefined
:
interface StringByString {
[key: string]: string;
const object: StringByString = {};
const value = object['nonExistingProp'];
value; // => undefined
const value: string
value
variable is a string
type according to TypeScript, however, its runtime value is undefined
.
The index signature simply maps a key type to a value type, and that’s all. If you don’t make that mapping correct, the value type can deviate from the actual runtime data type.
To make typing more accurate, mark the indexed value as string
or undefined
. Doing so, TypeScript is going to be aware that the properties you access might not exist:
interface StringByString {
[key: string]: string | undefined;
const object: StringByString = {};
const value = object['nonExistingProp'];
value; // => undefined
const value: string | undefined
3.2 String and number key
Let’s say that you have a dictionary of number names:
interface NumbersNames {
[key: string]: string
const names: NumbersNames = {
'1': 'one',
'2': 'two',
'3': 'three',
// etc...
Accessing a value by a string key works as expected:
const value1 = names['1'];
const value1: string
Would it be an error if you try to access a value by a number 1
?
const value2 = names[1];
const value2: string
Nope, all good!
JavaScript implicitly coerces numbers to strings when used as keys in property accessors (names[1]
is same as names['1']
). TypeScript performs this coercion too.
You can think that [key: string]
is the same as [key: string | number]
.
4. Conclusion
If you don’t know the structure of the object you’re going to work with, but you know the possible key and value types, then the index signature is what you need.
The index signature consists of the index name and its type in square brackets, followed by a colon and the value type: { [indexName: KeyType]: ValueType }
. KeyType
can be a string
, number
, or symbol
, while ValueType
can be any type.
Challenge: Write a generic type Indexed<K, V>
that creates an index signature where K
is the key type and V
is the value type. Share your solution in a comment below!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK