javascript - ¿Usando una instancia de Map para devolver un array de objetos distintos por nombre de propiedad?
Esta implementación parece estar funcionando bien (Stackblitz):
/**
* Returns all the elements that are distinct by the
* `property` value. Note that the implementation uses a `Map<string, E>` to
* index the entities by key. Therefore the more recent occurences
* matching a key instance will overwrite the previous ones.
*
* @param property The name of the property to check for distinct values by.
* @param entities The entities in the array.
*
* @example
* ```
* let todos:Todo = [{ id: 1, "Lets do it!" }, {id: 2, "All done!"}];
* let dtodos:Todo[] = distinct<Todo>(todos, 'id');
*/
export function distinct<E>(entities:E[], property:string):E[] {
let map:Map<string, E> = new Map();
entities.forEach((e:E)=>{
map.set(e[property], e);
});
return Array.from(map.values());
}
Lo único es que VSCode dibuja una línea ondulada roja debajo de la parte e[property] y el mensaje de error es:
El elemento tiene implícitamente un tipo 'any' porque el tipo '{}' no tiene una firma de índice.ts(7017)
¿Hay una manera de deshacerse de eso?
Implementación de la Tenencia de Biblioteca
He añadido la última implementación sugerida a este gestor de estado ligero para objetos y entidades:
https://www.npmjs.com/package/@fireflysemantics/slice
npm i @fireflysemantics/slice
...
import {distinct} from '@fireflysemantics/slice/utilities';
Demostración
https://stackblitz.com/edit/typescript-slice-distinct
Respuesta #1
El mensaje de error es un poco engañoso. Su problema es que no puede garantizar que e[property] sea del tipo string como has definido el Map.
Haz que la clave en el Mapa sea del tipo any ya que con tanta flexibilidad tampoco puedes determinar el tipo del valor.
Además, escribiría el parámetro property como keyof E para que TS asegure que solo puedo introducir nombres de propiedades válidos para el tipo.
function distinct<E>(entities:E[], property:keyof E):E[] {
let map:Map<any, E> = new Map();
entities.forEach((e:E)=>{
map.set(e[property], e);
});
return Array.from(map.values());
}
Respuesta #2
Basándonos en la respuesta de Thomas, podemos simplificar ambos:
- Código JavaScript: construyendo el
Mapde una vez; - Tipado en TypeScript: al añadir
K extends keyof E, podemos convertir las tuplas ([E[K], E]) usadas como parámetro de entrada del constructor deMapy eliminar el uso del tipoany.
Aquí el código:
function distinct<E, K extends keyof E>(entities: E[], property: K): E[] {
const entitiesByProperty = new Map(entities.map(e => [e[property], e] as [E[K], E]));
return Array.from(entitiesByProperty.values());
}
Al llamar a distinct(), no es necesario especificar los tipos genéricos ya que se pueden inferir. Aquí hay un ejemplo que funciona:
enum Status { Pending = 0, Done = 1 }
interface Todo { id: number, label: string, status: Status, date?: Date }
const todos: Todo[] = [
{ id: 1, label: 'Task 1', status: Status.Pending },
{ id: 2, label: 'Task 2', status: Status.Pending },
{ id: 1, label: 'Task 1', status: Status.Done },
];
distinct(todos, 'id'); // [{ id: 1, ... status: 1 }, { id: 2, ... status: 0 }]
