Расширяющий загрузчик распознает только ext2, а не ext1 и ext2.

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

Код:

interface ExtensionArgs {
    name: string;
}

class Extension<Args extends ExtensionArgs, AUI extends AutumnUI<any>> {
    #name: Args['name'];
    get name(): Args['name'] {
        return this.#name;
    }
    #autumnUI: AUI;
    get autumnUI() {
        return this.#autumnUI;
    }
    constructor(args: Args, aUI: AUI) {
        this.#name = args.name;
        this.#autumnUI = aUI;
    }
}

type Merge<A, B> = {
    [K in keyof A | keyof B]: K extends keyof A & keyof B
        ? A[K] | B[K]
        : K extends keyof B
            ? B[K]
            : K extends keyof A
                ? A[K]
                : never;
};

type AutumnUIBase = {
    extensions: {};
};

type AutumnUI<AUI extends AutumnUIBase> = {
    extensions: AUI['extensions'];
    add: <
        const AU extends AutumnUI<AutumnUIBase>,
        const callBack extends (arg: AU) => Extension<ExtensionArgs, AU>,
        const NewExtension extends ReturnType<callBack>,
    >(
        e: callBack,
    ) => {
        extensions: Merge<AU['extensions'], Record<NewExtension['name'], NewExtension>>;
        add: AutumnUI<AU>['add'];
    };
};

declare const createAutumnUI: AutumnUI<{
    extensions: {};
}>;

function ExtensionLoader<
    const AUI extends AutumnUI<AutumnUIBase>,
    const AUIC extends () => AUI,
    const EARGS extends ExtensionArgs,
>(unusedAUI: AUIC, arg: EARGS): (autumnUI: AUI) => Extension<EARGS, AUI> {
    return (autumnUI: AUI) => new Extension(arg, autumnUI);
}

const ext1 = ExtensionLoader(() => createAutumnUI, { name: 'ext1' });

const ext2 = ExtensionLoader(() => createAutumnUI.add(ext1), { name: 'ext2' });
//Показывает только команду от ext2, а не от ext1

const step1 = createAutumnUI.add(ext1).extensions.ext1;
const step2_0 = createAutumnUI.add(ext1).add(ext2).extensions.ext1; // должен быть доступен и здесь
const step2_1 = createAutumnUI.add(ext1).add(ext2).extensions.ext2;

после

createAutumnUI.add(ext1).add(ext2).extensions

Он должен знать о расширениях ext1 и ext2. но почему-то во время .add он теряет информацию о типе и знает только о последнем добавленном расширении. Как я могу изменить код, чтобы он знал о обоих расширениях?

Playground: https://tsplay.dev/N7D2Gm (67 строк)

Я пробовал много вещей и ожидаю, что это будет работать вместе, причина, по которой я в ExtensionLoader имею unusedAUI, заключается в том, что, определяя свое расширение, вы можете “зависеть” от конкретного набора загруженных расширений и строить на их основе. Позже, когда оно фактически загружает расширение, оно “строит” его только один раз.

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

Для решения проблемы, описанной в Вашем вопросе, необходимо убедиться, что функция add в интерфейсе AutumnUI корректно объединяет типы как новых, так и уже существующих расширений. Судя по коду, текущая реализация add теряет информацию о ранее загруженных расширениях при добавлении нового.

Основная проблема в том, что в типе Merge необходимо правильно формировать объединение типов, чтобы все расширения сохраняли свою типовую информацию при добавлении новых. Попробуем внести необходимые изменения.

Исправленный код

Изменим часть кода, касающуюся метода add и функции ExtensionLoader, чтобы они корректно обрабатывали расширения и сохраняли типы всех добавленных расширений.

type AutumnUI<AUI extends AutumnUIBase> = {
    extensions: AUI['extensions'];
    add: <
        const AU extends AutumnUI<AutumnUIBase>,
        const callBack extends (arg: AU) => Extension<ExtensionArgs, AU>,
        const NewExtension extends ReturnType<callBack>,
    >(
        e: callBack,
    ) => {
        extensions: Merge<AU['extensions'], Record<NewExtension['name'], NewExtension>>;
        add: AutumnUI<AU>['add'];
    } & { 
        // Обеспечим, что каждая новая добавленная функция добавляется к существующим
        extensions: Merge<AU['extensions'], Record<NewExtension['name'], NewExtension>>;
    };
};

function ExtensionLoader<
    const AUI extends AutumnUI<AutumnUIBase>,
    const AUIC extends () => AUI,
    const EARGS extends ExtensionArgs,
>(unusedAUI: AUIC, arg: EARGS): (autumnUI: AUI) => Extension<EARGS, AUI> {
    return (autumnUI: AUI) => new Extension(arg, autumnUI);
}

// Пример использования
const ext1 = ExtensionLoader(() => createAutumnUI, { name: 'ext1' });
const ext2 = ExtensionLoader(() => createAutumnUI.add(ext1), { name: 'ext2' });

const step1 = createAutumnUI.add(ext1).extensions.ext1;
const step2 = createAutumnUI.add(ext1).add(ext2).extensions;

// Все расширения теперь должны быть доступны.
const allExtensions = step2.ext1; // Это должно работать
const newExtension = step2.ext2; // Это тоже должно работать

Объяснение изменений

  1. Корректное объединение типов: Мы убедились, что метод add возвращает расширенные типы, которые включают как ранее добавленные расширения, так и новое, добавляемое расширение.

  2. Конструкция Merge: Убедитесь, что конструкция Merge позволяет объединять типы корректно, сохраняя их свойства и позволять их использовать позже.

  3. Тестирование изменений: После внесения этих изменений попробуйте протестировать код еще раз, добавляя и получая доступ ко всем расширениям.

Теперь, при добавлении ext1 и затем ext2, код должен сохранять информацию о обоих расширениях и позволять к ним обращаться.

Попробуйте протестировать данные изменения в приведенном вами Playground и убедитесь, что обе расширения теперь доступны, как ожидалось.

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

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