Búsqueda de sitios web

TypeScript: Buscar el tipo de un campo basado en otro campo


Digamos que tengo una interfaz así:

interface Cat {
  weight: number;
  name: string;
  adoptable: boolean;
}

Quiero definir la siguiente interfaz que se puede usar para establecer valores en Cat:

interface CatUpdate {
  field: keyof Cat;
  value: ???
}

y quiero que TypeScript haga cumplir que el valor sea del tipo apropiado según el campo. Por ejemplo, esto debería ser inválido:

const x: CatUpdate = {
  field: "name",
  value: 12
}

¿Cómo defino el tipo de CatUpdate.value?

Respuesta #1

Mi inclinación sería hacer que CatUpdate sea un tipo unión y no una interfaz, así:

interface Cat {
  weight: number;
  name: string;
  adoptable: boolean;
}

type PropUpdate<T extends object> = {
  [K in keyof T]: { field: K; value: T[K] }
}[keyof T];


type CatUpdate = PropUpdate<Cat>;
/* 
type CatUpdate = {
    field: "weight";
    value: number;
} | {
    field: "name";
    value: string;
} | {
    field: "adoptable";
    value: boolean;
}
*/

Eso debería funcionar lo suficientemente bien en el caso de uso que mencionaste:

const x: CatUpdate = {
  field: "name",
  value: 12
} // error!

Y deberías poder componer esos (CatUpdate | DogUpdate) si lo necesitas, aunque sin un caso de uso particular es difícil saber si tal composición es apropiada (¿Se pueden discriminar Cat y Dog entre sí? Es decir, ¿es Cat & Dog imposible? Si Cat & Dog es posible, entonces CatUpdate | DogUpdate solo se puede usar para actualizar un Cat & Dog. Si es imposible, entonces CatUpdate | DogUpdate probablemente debería reemplazarse con una unión discriminada como (CatUpdate & {kind: "CatUpdate"}) | (DogUpdate & {kind: "DogUpdate"}).)

Enlace al código

Respuesta #2

Una manera es usar un tipo genérico:

interface CatUpdate<T extends keyof Cat> {
  field: T;
  value: Cat[T];
}

Funciona, pero significa que necesitarás ser un poco más verboso al crear el objeto.

// Error
const x: CatUpdate<"name"> = {
  field: "name",
  value: 12
}

// No error
const y: CatUpdate<"name"> = {
  field: "name",
  value: "Dave"
}

Una solución sería usar una función para la instanciación que pueda inferir el tipo basado en los parámetros:

function getCatUpdate<T extends keyof Cat>(field: T, value: Cat[T]): CatUpdate<T> {
    return {
        field,
        value
    }
}

Como podrías llamar:

// Error
const x = getCatUpdate("name", 12);

// No error
const y = getCatUpdate("name", "Dave");

Artículos relacionados