TS Как по условию добавлять поля в тип
Есть тип User c двумя ролями Trainer и Client:
enum UserRole {
Trainer = 'Тренер',
Client = 'Клиент',
}
type User = {
userRole: UserRole,
}
Я хочу расширить этот тип и указать что в зависимости от того какая роль была выбрана, в этот тип будут добавлены поля из следующих интерфейсов:
export interface ITrainer {
certificate: string;
merits: string;
personalTraining: boolean;
}
export interface IClient {
caloriesToLose: number;
readyToTraining: boolean;
}
По итогу хочу получить что-то вроде этого:
type User = {
userRole: UserRole
} & User.userRole === UserRole.Trainer ? ITrainer : IClient
Прочитал про условные типы, но там они описаны для классов.
Может какой-нибудь infer тут можно использовать?
Ответы (2 шт):
Как вариант, могу предложить сделать через дженерик
enum UserRole {
Trainer = 'Тренер',
Client = 'Клиент',
}
export interface ITrainer {
certificate: string;
merits: string;
personalTraining: boolean;
}
export interface IClient {
caloriesToLose: number;
readyToTraining: boolean;
}
type UserExtendedType<T extends UserRole> = {
userRole: T,
} & (T extends UserRole.Trainer ? ITrainer : IClient)
// wrong userRole
const UserExtended: UserExtendedType<UserRole.Client> = { userRole: UserRole.Trainer, certificate: 'sss' };
// unknown field
const UserExtended2: UserExtendedType<UserRole.Trainer> = { userRole: UserRole.Trainer, certificate: 'sss', unknownField: 'test' };
// field from a wrong interface
const UserExtended3: UserExtendedType<UserRole.Trainer> = { userRole: UserRole.Trainer, caloriesToLose: 'sss' };
// Non full body
const UserExtended4: UserExtendedType<UserRole.Client> = { userRole: UserRole.Client, caloriesToLose: 1 };
// Correct
const UserExtended5: UserExtendedType<UserRole.Client> = { userRole: UserRole.Client, caloriesToLose: 1, readyToTraining: true };
Вообще-то, на мой взгляд самый нормальный способ - такой: песочница
enum UserRole {
Trainer = 'Тренер',
Client = 'Клиент',
}
export interface ITrainer {
userRole: UserRole.Trainer;
certificate: string;
merits: string;
personalTraining: boolean;
}
export interface IClient {
userRole: UserRole.Client;
caloriesToLose: number;
readyToTraining: boolean;
}
type User = ITrainer | IClient;
Если всё же по какой-то причине не хочется добавлять userRole в эти интерфейсы, то можно намутить такое: песочница
enum UserRole {
Trainer = 'Тренер',
Client = 'Клиент',
}
type User =
| { userRole: UserRole.Trainer } & ITrainer
| { userRole: UserRole.Client } & IClient
export interface ITrainer {
certificate: string;
merits: string;
personalTraining: boolean;
}
export interface IClient {
caloriesToLose: number;
readyToTraining: boolean;
}
Если типов больше, то можно немного упростить (заодно тайпскрипт проконтролирует, что перечислены все роли): песочница
enum UserRole {
Trainer = 'Тренер',
Client = 'Клиент',
}
type User = {
[role in UserRole]: { userRole: role } & {
[UserRole.Trainer]: ITrainer,
[UserRole.Client]: IClient,
}[role]
}[UserRole]
export interface ITrainer {
certificate: string;
merits: string;
personalTraining: boolean;
}
export interface IClient {
caloriesToLose: number;
readyToTraining: boolean;
}
Сюда же можно прикрутить общие поля из любого типа: песочница
enum UserRole {
Trainer = 'Тренер',
Client = 'Клиент',
}
type BaseUser = {
userRole: UserRole;
login: string;
passwordHash: string;
}
type IUser = Omit<BaseUser, "passwordHash"> & {
password: string;
}
type User = {
[role in UserRole]: { userRole: role } & IUser & {
[UserRole.Trainer]: ITrainer,
[UserRole.Client]: IClient,
}[role]
}[UserRole]
export interface ITrainer {
certificate: string;
merits: string;
personalTraining: boolean;
}
export interface IClient {
caloriesToLose: number;
readyToTraining: boolean;
}