Динамическое переопределение свойств интерфейса в TypeScript

Вопрос или проблема

Существует ли способ динамически обернуть каждое свойство в интерфейсе в обобщенный тип?

У меня есть несколько интерфейсов, которые определены в библиотеке. Я не могу их изменить (т.е. добавить обобщенные типы, например). Следующий интерфейс является примером одного из возможных интерфейсов, определенных в библиотеке.

interface Original {
    a: string;
    b: number;
    c: boolean;
}

У меня также есть этот обобщенный тип, который я использую для отображения значений в интерфейсе. (Т.е. каждый атрибут интерфейса будет иметь собственный svg-иконку, единицы измерения и т.д.) Я создал этот обобщенный тип сам.

interface GenericInterface<V> {
    value: V;
    units: string;
    errorMsg?: string;
    icons: ReactElement<any>;
}

Что я хочу сделать, так это обернуть каждый параметр в интерфейсе Original в GenericInterface. В результате я хочу получить следующее:

interface Modified {
    a: GenericInterface<string>;
    b: GenericInterface<number>;
    c: GenericInterface<boolean>;
}

Ближе всего к достижению этого результата я подошел так:

type ModifiedGeneric<T extends object, K extends keyof T> = Record<
    K,
    GenericInterface<T[K]>
>;

type Modified = ModifiedGeneric<
    Original,
    keyof Original
>;

Но это приводит к объединению всех типов параметров интерфейса. т.е.

interface Modified {
    a: GenericInterface<string | number | boolean>;
    b: GenericInterface<string | number | boolean>;
    c: GenericInterface<string | number | boolean>;
}

Кто-нибудь знает решение, которое я могу использовать для достижения желаемого результата?

Как указывает jonrsharpe в комментариях, просто используйте отображаемый тип:

interface Original {
    a: string;
    b: number;
    c: boolean;
}

interface GenericInterface<V> {
    value: V;
    units: string;
    errorMsg?: string;
    icons: React.ReactElement<any>;
}

type ModifiedGeneric<T extends {}> = {
    [key in keyof T]: GenericInterface<T[key]>
}

type ModifiedOriginal = ModifiedGeneric<Original>
// ModifiedOriginal имеет тип
// {
//   a: GenericInterface<string>;
//   b: GenericInterface<number>;
//   c: GenericInterface<boolean>;
// }

Playground

Обратите внимание, что второй обобщенный параметр не нужен, если вы не хотите иметь возможность работать с подмножеством keyof T.

Ответ или решение

В TypeScript существует возможность динамически оборачивать свойства интерфейса с помощью использованию отображаемых типов (mapped types). Это позволяет создать новый интерфейс, где каждое свойство исходного интерфейса будет обернуто в другой тип. Рассмотрим решение для вашей задачи, где мы хотим обернуть каждое свойство интерфейса Original в интерфейс GenericInterface.

Пример решения

  1. Исходный интерфейс:
    Вначале определим интерфейс, который вы получили из библиотеки:

    interface Original {
        a: string;
        b: number;
        c: boolean;
    }
  2. Общий интерфейс:
    У вас уже есть общий интерфейс GenericInterface, который мы будем использовать:

    interface GenericInterface<V> {
        value: V;
        units: string;
        errorMsg?: string;
        icons: React.ReactElement<any>;
    }
  3. Создание нового интерфейса с использованием отображаемых типов:
    Теперь давайте создадим новый тип ModifiedGeneric, который будет оборачивать каждое свойство интерфейса Original в GenericInterface. Для этого воспользуемся синтаксисом отображаемого типа:

    type ModifiedGeneric<T extends {}> = {
    };
  4. Использование нового типа:
    Мы можем теперь создать новый тип ModifiedOriginal, который будет соответствовать вашему ожиданию:

    type ModifiedOriginal = ModifiedGeneric<Original>;

    Таким образом, ModifiedOriginal будет представлять собой интерфейс, который имеет следующий вид:

    // Тип ModifiedOriginal будет:
    // {
    //    a: GenericInterface<string>;
    //    b: GenericInterface<number>;
    //    c: GenericInterface<boolean>;
    // }

Заключение

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

Это решение позволяет вам гибко адаптировать дополнительные интерфейсы, не меняя оригинал, сохраняя при этом типовую безопасность в приложении.

Оцените материал
Добавить комментарий

Капча загружается...