Búsqueda de sitios web

typescript - Tipado genérico de un hashmap con clave dinámica y arreglo de T


Intento crear una interfaz de tipado fuerte para un hashmap en TypeScript.

El hashmap contiene una clave con un nombre de cadena dinámico. Y un values que contiene un arreglo de tipo Genérico.

Intenté usar la siguiente interfaz:

export interface DynamicHashmap<T> {
  [dynamicKey: string]: string;
  values: T[];
}

pero no compilará y seguirá quejándose:

[ts] La propiedad 'values' de tipo 'T[]' no se puede asignar al tipo de índice de cadena 'string'.

Un ejemplo de valor generado correspondiente a este tipo, que agrupa valores según un atributo del objeto (aquí User.group). La dynamicKey se resuelve aquí como group.

const user1: User = { id: 'userValue1', group: 'someGroupId' };
const user2: User = { id: 'userValue2', group: 'someGroupId' }; 
const result = {
  group: 'someGroupId',
  values: [
    { id: 'userValue1', group: 'someGroupId' },
    { id: 'userValue2', group: 'someGroupId' }
  ]
}

Supongo que los valores estáticos interfieren con la clave dinámica.

¿Cómo puedo lograr la tipificación fuerte que estamos buscando?

Respuesta #1

La interfaz correcta es esta

export interface DynamicHashmap<T> {
  [key: string]: string | T[];
  values: T[];
}

Esto se debe a que, [key: string] representa una firma de todas las claves. Por eso se llama firma de índice.

Es posible que no estés muy satisfecho con tal interfaz, por eso deberías reconsiderarla para que sea algo diferente como:

export interface DynamicHashmap<T> {
  dynamicKey: string;
  dynamicValue: string;
  values: T[];
}

Respuesta #2

Normalmente, cuando escucho "dinámico" trato de entender si eso significa "conocido solo en tiempo de ejecución" o "conocido en tiempo de compilación pero con diferentes valores en diferentes lugares". Si es lo primero, entonces tienes que usar tipos muy amplios (como lo que estás mencionando indexado por cadena)... si es lo segundo, entonces a veces puedes usar genéricos para expresar un tipo más específico. Por ejemplo, en tu caso:

type DynamicHashMap<T, K extends keyof T> = Pick<T, K> & {values: T[]};

o

type DynamicHashMap<T, K extends keyof T> = { 
  [P in K | 'values']: P extends K ? T[K] : T[] 
};

es genérico tanto en T, como en K, una clave del tipo T. Puedes usar la inferencia de tipos para instanciar tanto T como K, de la siguiente manera:

// for example
function makeDynamicHashMap<T, K extends keyof T>(
  key: K, 
  value: T[K], 
  values: T[]
): DynamicHashMap<T, K> {
  return { [key]: value, values: values.filter(v => v[key] === value) } as any;
}

const result = makeDynamicHashMap("group", "someGroupId", [user1, user2]);
result.group; // string
result.values; // User[]

Entonces result se infiere como tipo DynamicHashMap<User, "group">. ¿Ayuda eso? ¡Buena suerte!