- Published on
Day16: Learning TypeScript - A Good Method to Add Arbitrary Properties: Index Signatures
- Authors

- Name
- irisjustdoit
- @irisjustdoit
Day16: Learning TypeScript - A Good Method to Add Arbitrary Properties: Index Signatures
In the previous article about interfaces, I learned that we can use Index Signatures. I found that there are some things to pay attention to when using them, so I decided to write a separate article about it. (And secretly thought, yay, another article XD)
Index Signatures are used when we sometimes do not know the property names in advance or when we will add properties in the future, but we know the shape of the values. We can use Index Signatures to describe the possible types of values.
Defining an Index Signature
In the following example, we define arbitrary properties using [propName: string], where the property name (key) is a string, and the value can be named freely. The corresponding value (value) can be of type string, number, or undefined. At this point, we can add properties, as shown in the example below, where adding gender is also fine. (Generally, adding properties that are not defined in an interface will cause an error.)
The name [propName: string] can be defined freely, such as changing it to [key: string], which has the same effect. It can be of string or number type; for numbers, it can be [index: number].
interface IPerson {
name: string;
age?: number;
[propName: string]: string | number | undefined;
}
const iris: IPerson = {
name: 'Iris',
gender: 'female'
};
All Used Properties Must Conform to the Description of Index Signatures
Example 1
interface IPerson {
name: string;
age?: number;
[propName: string]: string | number | undefined;
}
const iris: IPerson = {
name: 'Iris',
gender: 'female'
};
According to the above example, is there anything strange about why we need to add undefined? This is because all properties we declare must conform to the types described by the Index Signatures.
I initially did not add undefined, and it caused an error. Later, I realized that since age is an optional property, if it is not assigned a value, it will be undefined. Of course, this is also because we have enabled strictNullChecks in tsconfig, so the compiler will perform special checks, and when it detects that age could be undefined, it will remind us that it is not assignable, as it does not match the type defined by the Index Signatures. (I struggled with this for a while.)

Example 2
I found my writing too poor, and I don't know if you can understand it. Let me give a more intuitive example. In the example below, when the type of name is defined as string, it causes an error because the Index Signatures define the value type as only numbers. All member property formats must conform to the Index Signatures.
// error example
interface NumberDictionary {
[index: string]: number;
length: number; // ok
name: string; //error: Property 'name' of type 'string' is not assignable to 'string' index type 'number'.
}
// ok example
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number; // ok, length is a number
name: string; // ok, name is a string
}

Can Use readonly
You can also give Index Signatures a readonly property to prevent properties from being modified.
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = {
1: 'a',
2: 'b'
}
myArray[2] = "bbb"; //error: Index signature in type 'ReadonlyStringArray' only permits reading.

String Literal Types & Mapped Types Applications
String Literal Types refer to certain special "values" that can be used as "types," constraining the values to only be one of several specific strings.
Mapped Types apply the concept of looping through Index using in. [k in Index]? means that when adding properties, the properties can only be those defined in Index, such as 'a' | 'b' | 'c'. In the example below, if you add a d property, it will cause an error reminding you that it does not exist in the type of FromIndex.
[k in Index]? allows you to name k freely, and ? indicates that it is an optional property, as shown in the example below, where not assigning the a property is acceptable.
//String Literal Type
type Index = 'a' | 'b' | 'c';
// Mapped Types
type FromIndex = {
[k in Index]?: number
};
const good: FromIndex = {
b: 1,
c: 2
};
const bad: FromIndex = {
b:1,
c:2,
d:3
//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'.
};
Mapped Types are usually used together with keyof. Interested friends can check out PJ's detailed article here, which is very detailed. But for advanced topics, I will learn them later; for now, let's digest the simple ones. (Life is hard QQ)
Having Both String and Number Type Indexers
Although it is not common, TypeScript supports having both string and number type indexers.
interface ArrStr {
[key: string]: string | number; // Must accommodate all members
[index: number]: string; // Can be a subset of string indexer
// Just an example member
length: number;
}
Thank you for reading! See you tomorrow! By the way, the Ironman race is so exhausting. Today at work, I almost couldn't get up. I truly admire all the participants!
References
https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures https://basarat.gitbook.io/typescript/type-system/index-signatures#declaring-an-index-signature https://jkchao.github.io/typescript-book-chinese/typings/indexSignatures.html#%E4%BD%BF%E7%94%A8%E4%B8%80%E7%BB%84%E6%9C%89%E9%99%90%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%AD%97%E9%9D%A2%E9%87%8F https://pjchender.blogspot.com/2021/08/typescript-mapped-type.html
