Вопрос или проблема
Код:
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; // Это тоже должно работать
Объяснение изменений
-
Корректное объединение типов: Мы убедились, что метод
add
возвращает расширенные типы, которые включают как ранее добавленные расширения, так и новое, добавляемое расширение. -
Конструкция
Merge
: Убедитесь, что конструкцияMerge
позволяет объединять типы корректно, сохраняя их свойства и позволять их использовать позже. -
Тестирование изменений: После внесения этих изменений попробуйте протестировать код еще раз, добавляя и получая доступ ко всем расширениям.
Теперь, при добавлении ext1
и затем ext2
, код должен сохранять информацию о обоих расширениях и позволять к ним обращаться.
Попробуйте протестировать данные изменения в приведенном вами Playground и убедитесь, что обе расширения теперь доступны, как ожидалось.