Búsqueda de sitios web

typescript - Nombre de propiedad condicional en interfaz de TypeScript dependiendo del tipo pasado


El título suena confuso, pero aquí está la esencia.

Tengo una interfaz User que puede o no tener algunas propiedades dependiendo de dónde se obtenga. Esto es lo que quiero decir: (observa las propiedades opcionales role y client_details)

export interface User {
  id: string;
  first_name: string;
  last_name: string;
  email: string;

  // -- One of these in undefined depending on fetch location
  role?: Role;
  client_details?: ClientDetails;
}

El enfoque anterior funciona, sin embargo tener un valor indefinido cada vez no se ve muy bien. Sería más ordenado si pudiera hacer esto:

export interface User<ExtraType = Role | ClientDetails> {
  id: string;
  first_name: string;
  last_name: string;
  email: string;

  // -- Dynamic property name instead, much cleaner approach
  [ExtraType === Role ? 'role' : 'client_details']: ExtraType;
}

Esto no funciona y genera un montón de errores, principalmente No se puede que un nombre de propiedad computada haga referencia a un parámetro de tipo de su tipo contenedor.

Soy consciente de que puedo simplemente crear una propiedad estática extra y simplemente usarla cada vez, pero quería que el nombre de la propiedad tuviera algo de sentido. ¿Es esto factible en absoluto? ¡Gracias!

Respuesta #1

Lo que necesitas aquí es una unión disjunta de tipos. En tu caso, se puede hacer así:

interface UserBase {
  id: string;
  first_name: string;
  last_name: string;
  email: string;
}
interface UserWithRole extends UserBase {
  role: Role;
  client_details?: undefined;
}
interface UserWithDetails extends UserBase {
  client_details: ClientDetails;
  role?: undefined;
}

export type User = UserWithRole | UserWithDetails;

Ahora, dondequiera que tengas un valor del tipo User, puedes comprobarlo así:

function test(user: User) {
  if (user.role) {
    // here typescript knows that user is of type UserWithRole
  } else {
    // and here it knows that user is of type UserWithDetails
  }
}

He agregado una declaración de propiedad faltante (property?: undefined) a ambos tipos para que TypeScript no se queje de que User no siempre tiene la propiedad .role; puedes eliminarla y ver el error en la línea if (user.role).

Aquí está el ejemplo completo