When it comes to data types, avoid using primitive type names in uppercase. Instead of String
, opt for string
.
In TypeScript, the parameter type for an index signature must be either string
, number
, or symbol
. The usage of symbol
is limited to --target es2015
or higher.
If you want to restrict the key type to a subset of string values, a type
alias declaration should be used instead of an interface
.
export type HashMap<K extends string, V> = {
[P in K]?: V;
}
The syntax [P in K]
iterates through each string literal type within the specified set of strings represented by K
.
This approach is beneficial as it allows us to limit the contents of the map by defining a union type with string literals.
For instance:
const map: HashMap<'firstName' | 'lastName', string> = {
firstName: 'John', // OK
nickname: 'Johnny' // Error
};
Essentially, the key type should be a string, but it can be constrained to specific strings or a defined set of strings using a union type.
In practice, the string union type often depends on another type.
For example:
interface Item {
name: string;
id: number;
}
interface PropertyMetadata {
kind: 'data' | 'accessor';
}
type LazyItem = {
[P in keyof Item]: PropertyDescriptor
};
keyof
is a Type Operator that generates a type consisting of the property keys as string unions.
If utilizing an arbitrary key type bound by certain constraints, consider using an ES2015 Map
object. Before the introduction of this type in JavaScript, achieving this mapping cleanly was challenging and string
was predominantly used as a key type.
By leveraging an ES2015 map alongside TypeScript generics, one can approximate the desired functionality.
For example:
interface Category {
name: string;
products: Product[];
}
interface Product {
name: string;
category: Category;
}
const categoriesToProducts = new Map<Category, Product[]>();
declare function getProducts(): Product[];
const products = getProducts();
products.forEach(product => {
const mapped = categoriesToProducts.get(product.category);
if (mapped) {
mapped.push(product);
}
else {
categoriesToProducts.add(product.category, [product]);
}
});