Вот класс:
const COMMANDS = [
"get-myanimelist-staff-urls",
"get-myanimelist-anime-explicit-genres",
"get-myanimelist-anime-genres",
"get-myanimelist-anime-themes"
...
];
class Shiinobi {
constructor() {
COMMANDS.forEach((command) => {
this[command.replaceAll("-", "_")] = async (...args: any[]) => {
const id = args[0];
return await this.#spawn({ command, id: id });
};
});
}
...
и я использую этот класс Shiinobi
например:
const shiinobi = new Shiinobi();
const res = await shiinobi.get_myanimelist_staff_urls();
По сути, это правильный код, поскольку constructor
создает такие методы.
но для таких методов нет типа:
shiinobi.get_myanimelist_staff_urls()
этот код показывает ts-error
Я пробовал использовать машинописный текст, изготовленный по индивидуальному заказу Utility
, но надежды не было.
Редактировать:
вот код репозитория GitHub
Вы можете сначала объявить некоторые типы:
const COMMANDS = [
"get-myanimelist-staff-urls",
"get-myanimelist-anime-explicit-genres",
"get-myanimelist-anime-genres",
"get-myanimelist-anime-themes"
// ...
] as const;
type COMMANDS_TYPE = typeof COMMANDS;
type ReplaceDashWithUnderscore<T extends string> =
T extends `${infer P1}-${infer R1}` ? `${P1}_${ReplaceDashWithUnderscore<R1>}` :
T extends `${infer P2}-${infer R2}` ? `${P2}_${ReplaceDashWithUnderscore<R2>}` :
T extends `${infer P3}-${infer R3}` ? `${P3}_${ReplaceDashWithUnderscore<R3>}` :
T extends `${infer P4}-${infer R4}` ? `${P4}_${ReplaceDashWithUnderscore<R4>}` :
T extends `${infer P5}-${infer R5}` ? `${P5}_${ReplaceDashWithUnderscore<R5>}` :
T;
type Underscore<T extends COMMANDS_TYPE> = {
[K in keyof T]: T[K] extends string ? ReplaceDashWithUnderscore<T[K]> : never;
};
type ToObj<T extends Underscore<COMMANDS_TYPE>> = {
// TODO Should Promise<void> be Promise<return type of spawn>
[K in T[number]]: (id: string, ...args: any[]) => Promise<void>;
};
Вышеупомянутое позволяет вам определить объект с помощью команд как методов в правильном формате:
Тогда вместо использования класса вы можете использовать фабрику для литерала объекта. С этим будет проще справиться при использовании динамических свойств:
function shinobiFactory() {
// private stuff
const somethingPrivate: number = 0;
interface SpawnConfig {
command: typeof COMMANDS[number];
id: string;
}
function spawn(config: SpawnConfig) {
return somethingPrivate;
}
function someOtherMethod() {
return somethingPrivate;
}
function underscore<T extends string>(s: T) {
return s.split("-").join("_") as ReplaceDashWithUnderscore<T>;
}
const commandProps = COMMANDS.reduce(
(prev, curr) => {
const key = underscore(curr)
const value = async (id: string, ...args: any[]) => {
return await spawn({ command: curr, id: id });
};
const update = {
...prev,
...{
[key]: value
}
};
return update;
},
{}
) as ToObj<Underscore<COMMANDS_TYPE>>;
// Return only what you want to be public
return {
...commandProps,
someOtherMethod
}
}
Наконец, вы можете вызвать метод для создания экземпляров вашего «класса». Как видите, и свойства, и аргументы верны:
В итоге я использовал специальную утилиту , которая заменяет -
на _
и расширяет интерфейс Shiinobi
.
Вот измененный код:
type Command = (typeof COMMANDS)[number];
type ReplaceHyphens<T extends string> = T extends `${infer P1}-${infer P2}`
? `${P1}_${ReplaceHyphens<P2>}`
: T;
type ShiinobiProperties = {
[K in Command as ReplaceHyphens<K>]: (id?: number) => Promise<any>;
};
interface Shiinobi extends ShiinobiProperties {}
А вот пример минимального воспроизведения