Генерация динамических типов в машинописном тексте

Вот класс:

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

Привет, пожалуйста, можете ли вы предоставить всю информацию об ошибке.

Derek Roberts 18.08.2024 02:55

Короткие вопросы: 1. Вам нужно, чтобы динамические методы были доступны изнутри класса Shiinobi? 2. Каким должен быть тип возвращаемого значения для ваших методов?

moonstar-x 18.08.2024 04:42

Добро пожаловать в Stack Overflow! • Пожалуйста, отредактируйте , чтобы предоставить минимально воспроизводимый пример без ссылок на необъявленные вещи (например, #spawn), но со всей информацией о полученном вами сообщении об ошибке.

jcalz 18.08.2024 18:50

(см. предыдущий комментарий) • Чего именно вы ожидаете? TS не поддерживает по-настоящему динамические ключи в объявлении класса; самое близкое, что вы можете получить, — это индексная подпись. Должны ли ключи быть статически известны из содержимого COMMANDS? Если да, то replaceAll() недостаточно строго типизирован для преобразования типов строковых литералов. Я мог бы предположить что-то вроде это то, что вы хотите, но было бы очень полезно, если бы вы отредактировали, чтобы уточнить.

jcalz 18.08.2024 18:50

@DerekRoberts журналов ошибок не было, просто машинописный текст не мог понять их типы. Я заставил его замолчать с помощью // @ts-ignore

moonlitgrace 19.08.2024 06:07

@moonstar-x 1. Нет, я не хочу получать к нему доступ изнутри (извините, я забыл добавить полный код класса). 2. Это должно быть обещание типа:Promise<any>

moonlitgrace 19.08.2024 06:09

@jcalz да, извини, только что сделал :)

moonlitgrace 19.08.2024 06:10

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

moonlitgrace 19.08.2024 06:16

«Журналов ошибок не было, просто машинописный текст не мог понять их типы». Итак, были журналы ошибок, когда вы запускали tsc или вводили ошибки в вашей IDE. Пожалуйста, отредактируйте , чтобы включить их. См. мой комментарий о минимально воспроизводимом примере , включая #spawn и т. д. Согласно коду, который вы здесь представили, ваш тип утилиты не должен работать, потому что COMMANDS имеет тип string[] (может быть, вы хотите as const там, но здесь его нет). ). Я вижу, что у вас есть ссылка на ваш код на github, но это не считается минимально воспроизводимым примером. Весь соответствующий код должен быть в вопросе в виде обычного текста.

jcalz 19.08.2024 15:26

(пожалуйста, посмотрите предыдущий комментарий и ответьте). Если вы нашли исправление (хотя оно похоже на другие ответы, представленные здесь), то его следует опубликовать в ответе, а не как редактирование вопроса. Просьбы о лучших способах выполнения задач становятся менее подходящими для Stack Overflow и более подходящими для Code Review , поэтому я бы посоветовал вам не делать этого здесь. Также обратите внимание, что нигде в вопросе вы не указали, что хотите (id?: number) => Promise<any>, это только в вашем отредактированном решении. Если вы опубликуете ответ, нам все равно понадобится минимально воспроизводимый пример, который его мотивирует.

jcalz 19.08.2024 15:30

@jcalz привет, извини, я не знал о стандартах Stack Overflow (это был мой первый раз). в любом случае, я опубликовал ответ с примером. надеюсь, в этот раз все будет хорошо :)

moonlitgrace 20.08.2024 19:05
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
11
63
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете сначала объявить некоторые типы:

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
  }
  
}

Наконец, вы можете вызвать метод для создания экземпляров вашего «класса». Как видите, и свойства, и аргументы верны:

Демо-версия игровой площадки

привет, да, это действительно близко к тому, что мне нужно (извините, я не смог добавить полные требования). Мне удалось исправить это с помощью такой специальной утилиты, я новичок в машинописном тексте, можете ли вы проверить мой отредактированный вопрос? Спасибо :)

moonlitgrace 19.08.2024 06:21
Ответ принят как подходящий

В итоге я использовал специальную утилиту , которая заменяет - на _ и расширяет интерфейс 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 {}

А вот пример минимального воспроизведения

Другие вопросы по теме