TS динамический интерфейс

Я пишу игру про майнинг, и мне нужно работать с разными типами комплектующих: GPU, platform, RAM, PSU, case. У всех много различных полей. И в БД я решил сделать одну таблицу parts, где перечислены все характеристики всех комплектующих. И если в БД запись с типом GPU, то заполнены поля ТОЛЬКО для GPU, а остальные NULL.

То есть поля для Part зависят от Part.type. Как описать такое поведение через интерфейс или класс?

Вот мои интерфейсы.

export type PartType = 'GPU' | 'platform' | 'RAM' | 'PSU' | 'case';

export interface IBasePart {
    id?: number;
    name: string;
    image: string;
    vendor: string;
    slug: string;
    type: PartType;
    price: number; // In $$$
    created_at?: string;
    updated_at?: string;

    _image?: File | null;
}

export interface IGPU extends IBasePart {
    GPU_VRAM_size: number;
    GPU_VRAM_frequency: number;
    GPU_VRAM_type: number;
    GPU_fans_count: number;
    GPU_fans_efficiency: number;
}

export interface IPlatform extends IBasePart {
    platform_cors_count: number;
    platform_threads_count: number;
    platform_frequency: number;
    platform_RAM_slots: number;
}

export interface IRAM extends IBasePart {
    RAM_frequency: number;
    RAM_size: number;
    RAM_channels: number;
}

export type PSU_EfficiencyType = 'none' | 'bronze' | 'silver' | 'gold' | 'platinum' | 'titanium';

export interface IPSU extends IBasePart {
    PSU_power_supply: number; // WATT
    PSU_efficiency: PSU_EfficiencyType;
}

export type CaseMaterialType = 'wood' | 'iron' | 'aluminium';

export interface ICase extends IBasePart {
    case_material: CaseMaterialType;
    case_material_rus: string;
    case_GPUs_slots: number;
    case_critical_temp: number;
}

// -----------------------

export interface IPart extends IBasePart, IGPU, IPlatform, IRAM, IPSU, ICase {

}

// Или, может, так
export type PartType<T> = T & IBasePart;

Ответы (2 шт):

Автор решения: EzioMercer

Вам скорее всего нужно прочитать эту часть документации. Там очень подробно расписано как наследовать нужный интерфейс в зависимости от условий

Как один из вариантов, можете попробовать сделать так:

interface IGPU {
    one: number
}

interface IPlatform {
    two: number
}

interface IRAM  {
    three: number
}

type Part<T extends 'IGPU' | 'IPlatform' | 'IRAM'> = 
    T extends 'IGPU' ? IGPU :
     T extends 'IPlatform' ? IPlatform : IRAM;


const part1: Part<'IGPU'> = {one: 1};
const part2: Part<'IPlatform'> = {two: 2};
const part3: Part<'IRAM'> = {three: 3};
→ Ссылка
Автор решения: Grundy

В данном случае стоит воспользоваться discriminated unions

для этого нужно убрать из общей части поле type, по которой можно определить конкретный тип, и добавить его с нужным значением в конкретные типы

пример:

export interface IBasePart {
    id?: number;
    name: string;
    image: string;
    vendor: string;
    slug: string;
    price: number; // In $$$
    created_at?: string;
    updated_at?: string;

    _image?: File | null;
}

export interface IGPU {
    type: 'GPU';
    GPU_VRAM_size: number;
    GPU_VRAM_frequency: number;
    GPU_VRAM_type: number;
    GPU_fans_count: number;
    GPU_fans_efficiency: number;
}

export interface IPlatform {
    type: 'platform';
    platform_cors_count: number;
    platform_threads_count: number;
    platform_frequency: number;
    platform_RAM_slots: number;
}

export type Part = IBasePart & (IGPU | IPlatform) // Конечный тип

function fun(p : Part){
    console.log(p.id); // base fields
    if (p.type === 'platform') {
        console.log(p.platform_cors_count) // fields from IPlatform
    }else {
        console.log(p.GPU_fans_count) // fields from IGPU
    }
}

Playground Link

→ Ссылка