Búsqueda de sitios web

typescript - Genéricos y arreglos: ¿por qué termino con `Generic` en lugar de `Generic[]`?


Tengo una función simple que puede aceptar un solo objeto de tipo T o un arreglo de objetos de tipo T[]. Luego hará su trabajo y devolverá un resultado que coincida con el tipo pasado (es decir, si se pasa un arreglo, se devuelve un arreglo de resultados, y si se pasa un solo elemento, se devuelve un único resultado).

El JS transpilado funciona exactamente como se esperaba, pero el sistema de tipos sigue insistiendo en anidar el arreglo dentro del genérico y no estoy seguro de por qué ni de cómo asegurar que se resuelva como quiero.

Función de Ejemplo

  • Ejemplo de patio de juegos
type OneOrMany<T> = T | T[]
type Item = Record<string, any>
type CapitalizedProps<T extends Item> = {
  [K in keyof T as Capitalize<K & string>]: T[K]
}

function toCapitalizedProps<T extends Item>(item: T): CapitalizedProps<T>
function toCapitalizedProps<T extends Item>(items: T[]): CapitalizedProps<T>[]
function toCapitalizedProps<T extends Item>(
  itemOrItems: OneOrMany<T>,
): OneOrMany<CapitalizedProps<T>> {
  if (Array.isArray(itemOrItems)) {
    return itemOrItems.map((item) =>
      toCapitalizedProps(item),
    ) as CapitalizedProps<T>[]
  }

  const result = { ...itemOrItems }

  for (const key in result) {
    result[(key[0].toUpperCase() + key.slice(1)) as keyof T] = result[key]
    delete result[key]
  }

  return result as unknown as CapitalizedProps<T>
}

Respuesta #1

Las sobrecargas de función utilizan la primera coincidencia.

function toCapitalizedProps<T extends Item>(item: T): CapitalizedProps<T>
function toCapitalizedProps<T extends Item>(items: T[]): CapitalizedProps<T>[]

Y resulta que un arreglo coincide con la restricción Item de Record<string, any>, porque un arreglo tiene propiedades de tipo cadena que todos tienen valores asignables a any.

const test: Record<string, any> = [] as string[] // fine

Entonces, cuando pasas un arreglo, se utiliza la primera sobrecarga, la cual infiere T como un tipo de arreglo.


Si cambias el orden, entonces la sobrecarga que necesita ser un arreglo será verificada primero y utilizada si es un arreglo. Si no, pasará a la siguiente.

function toCapitalizedProps<T extends Item>(items: T[]): CapitalizedProps<T>[]
function toCapitalizedProps<T extends Item>(item: T): CapitalizedProps<T>

Ver el patio de recreo

Respuesta #2

Las anulaciones se corresponden en el orden de su definición.

T no tiene restricciones que le impidan coincidir con { name: string; age: number; }[], por lo que toCapitalizedProps devuelve CapitalizedProps<T> donde T es { name: string; age: number; }[]

Si inviertes ese orden, se soluciona tu problema:

function toCapitalizedProps<T extends Item>(items: T[]): CapitalizedProps<T>[]
function toCapitalizedProps<T extends Item>(item: T): CapitalizedProps<T>
function toCapitalizedProps<T extends Item>() {
...

}

Parque infantil