Задать вопрос
  • Как типизировать переменную под хранение классов?

    johnymkp
    @johnymkp Автор вопроса
    Alexandroppolus, новый вариант подошел, спасибо!
    Написано
  • Как типизировать переменную под хранение классов?

    johnymkp
    @johnymkp Автор вопроса
    Alexandroppolus, мне в общем-то и не надо, чтобы он был утилитой. Вполне можно сделать его полностью самостоятельным.

    В вашем примере решение смотрится хорошо, т.к. он компактный. Но при адаптации у меня возникли такие проблемы:
    1. В классе MyDatabase я использую синглтон паттерн. Поэтому, когда я добавил дженерик к самому классу БД, компилятор начал ругаться "Static members cannot reference class type parameters" на статическое поле private static instance: MyDatabase;
    2. В конфиге находятся не только конфигурации хранилища, но и некоторые параметры самой БД, вроде имени и версии. Поэтому возникает дополнительный слой вложенности в конструкции InstanceType M[N]['oftype'], которая и так довольно трудно воспринимается.

    В итоге у меня сложилось впечатление, что в этом месте от типизации как будто больше возни, чем пользы. Так если прикинуть, в коде, который извлекает хранилище, я и так знаю тип хранилища, который мне нужен. Поэтому можно сделать просто вот так:
    public getStorage<T>(storageName: StorageName): T  {
        return this.storages.get(storageName) as T;
      }

    а при извлечении вот так
    const matchStorage = database.getStorage<MatchStorage>(StorageName.Matches);

    Как думаете, норм?
    Написано
  • Как типизировать переменную под хранение классов?

    johnymkp
    @johnymkp Автор вопроса
    Alexandroppolus, я вроде бы понял как работает InstanceType, но пока не получается адаптировать пример под свой код. Правильно ли я понимаю, что ваше решение базируется на том, что обязательно надо вводить дженерик на классе Database и иначе никак?
    Написано
  • Как типизировать переменную под хранение классов?

    johnymkp
    @johnymkp Автор вопроса
    Alexandroppolus, спасибо, когда разберусь, напишу что вышло.
    Написано
  • Как типизировать переменную под хранение классов?

    johnymkp
    @johnymkp Автор вопроса
    Alexandroppolus, сейчас попробую вкратце объяснить что к чему у меня там.

    database это класс, изначально основная задача которого - это принять конфиг с названием\версией БД, настройками хранилищ (имя, keyPath, индексы и т.д.) и создать БД и хранилища. Ну и вернуть объект этой БД.
    Потом я уже создал дженерик-класс Storage, реализовал в нем crud.

    Сперва я просто создавал в начале программы хранилища и передавал в них БД.

    Но потом подумал, что было бы удобнее просто у экземпляра БД попросить "дай мне хранилище для работы с Match". Поэтому я решил при создании indexeddb-хранилищ тут же создавать и экземпляры Storage, типизированные Match'ем и прочими типами данных, сохранять эти Storage в map и сделать метод получения из нее по имени. Это удобно еще и тем, что при создании Storage-экземпляра можно сразу ему передать БД.

    Вот для этого я и добавил в исходный конфиг еще и классы конкретных хранилищ.

    Так что да, database сразу создается со всеми возможными хранилищами.

    P.S. Я вот еще подумал, мб можно хранить как any, а при извлечении приводить к нужному типу как-то через as например.
    Написано
  • Как типизировать переменную под хранение классов?

    johnymkp
    @johnymkp Автор вопроса
    Кажется я понял о чем вы говорите. Когда я сделал поле под хранилища
    private storages: Map<StorageName, IStorage<any, any>>;

    и потом попробовал из него извлечь хранилище и передать в сервис
    const matchStorage = database.getStorage(StorageName.Matches);
      const ms = new MatchService(matchStorage);

    то столкнулся с ошибкой что MatchStorage не совместим с Storage{any, any}.
    Теперь я понял, о чем вы. Но тогда я не знаю как решить эту проблему.

    Получается невозможно что ли создать типизированные хранилища, сохранить из в мапу например, а потом извлекать по имени?
    Написано
  • Как типизировать переменную под хранение классов?

    johnymkp
    @johnymkp Автор вопроса
    Alexandroppolus, а мне эти конфиги доставать не надо. Они только в одну сторону нужны - чтобы создать экземпляры хранилищ, типизированные нужными типами данных. Основная проблема, которую я хотел решить всеми этими дженериками - это при работе с БД внести ясность - что сохраняешь, что читаешь. Вот есть сервис для работы с матчами. Он из БД загружает матчи, может там что-то по ним посчитать и т.д. Может быть еще какому-то сервису потребуются матчи. Они что, должны считать массив unknown объектов и сами трансформировать их в матчи? Я вижу это как если бы я пришел в магазин за яйцами, а мне дали курицу и сказали "Яйца из нее сам доставай". А так у меня есть условный MatchStorage класс, который типизирует Storage нужным типом данных (Match) и я этот MatchStorage передаю всем, кому нужно работать с матчами. Они вызывают read и получают массив Match, а не массив unknown.
    Написано
  • Как типизировать переменную под хранение классов?

    johnymkp
    @johnymkp Автор вопроса
    Alexandroppolus, unknown в итоге не подходит, потому что он тянется до самого верха. Если Storage прочитает данные и вернет их как unknown, то чтобы пользоваться ими, придется явно преобразовывать к нужному типу. Делать это в клиентском коде не хочется, потому что логичнее, что если клиентский код работает с типом Match и запрашивает чтение из БД, то БД ему должна возвращать Match, а не unknown. Поэтому дженерики выглядят лучше.
    Написано
  • Как типизировать переменную под хранение классов?

    johnymkp
    @johnymkp Автор вопроса
    Alexandroppolus, не могу сходу сказать, насколько что-то изменится, если написать unknown. Но я попробую переделать места, которых это все касается. Если окажется, что дженерики и правда не нужны, тогда отмечу решением. А если какие-нибудь проблемы по ходу возникнут из-за unknown, то еще напишу сюда.
    Написано
  • Как типизировать переменную под хранение классов?

    johnymkp
    @johnymkp Автор вопроса
    P.S. Про "негде закрыть" я похоже ошибся. Могу же написать вот так:
    export interface IDbConfig {
      dbname: string;
      version: number;
      storages: IStorageConfig<any, any>[];  // Закрываю типы тут
    }
    
    export interface IStorageConfig<T, K> {
      storageName: StorageName;
      oftype: new (database: IDBDatabase, storageName: StorageName) => IStorage<T, K>;

    Тогда если сам объект конфига напишу вот так:
    export const matchStorageConfig: IStorageConfig<Match, number> = {
      storageName: StorageName.Matches,
      oftype: MatchStorage,

    то компилятор не позволит положить в oftype конструктор, который реализует, например Storage<string, number>, а только тот, что реализует Storage<Match, number>

    Вопрос про any остается - в данном случае any это нормально?
    Написано
  • Как типизировать переменную под хранение классов?

    johnymkp
    @johnymkp Автор вопроса
    Написать
    IStorageConfig<T, K>
    не могу, потому что у меня этот интерфейс используется в конфиге всей БД, вот так:
    export interface IDbConfig {
      dbname: string;
      version: number;
      storages: IStorageConfig[];  // ЗДЕСЬ
    }

    Соответственно, закрыть T и K негде.
    Параметры у конструкторов всех хранилищ одинаковые. Я написал вот так:
    export interface IStorageConfig {
      storageName: StorageName;
      oftype: new (database: IDBDatabase, storageName: string) => IStorage<any, any>;

    Нормально? В плане того, что any в данном случае это не моветон? Как раз как будто бы логично выглядит, что тип данных и ключ хранилища может быть любой.
    Написано
  • Как типизировать переменную под хранение классов?

    johnymkp
    @johnymkp Автор вопроса
    Василий Банников, oftype используется примерно так:
    private createStorageInstances(connection: IDBDatabase) {
      this.storages = new Map();
      this.config.storages.forEach(s => {
        if (s.oftype) {
          this.storages.set(s.storageName, new s.oftype(connection, s.storageName));  // ТУТъ
        }
      });
    }
    
    public getStorage(storageName: StorageName) {
      return this.storages.get(storageName);
    }


    А "К" нужен просто чтобы тип ключа задать, больше ни для чего не используется. Интерфейс IStorage имеет методы записи, чтения, удаления. Допустим, метод чтения принимает ключ типа K, а возвращает массив типа T (массив лежит внутри DbOperationResult, там массив успешно считанных данных и массив ошибок, если что-то не считалось):
    public read(key?: K | K[]): Promise<DbOperationResult<T>> {

    Вот так К и используется. Вся логика работы с хранилищами реализована внутри класса Storage. А "конкретные" классы вроде MatchStorage нужны просто чтобы закрыть типы в Storage:
    export class MatchStorage extends Storage<Match, number> {
      public constructor(database: IDBDatabase, storageName: string) {
        super(database, storageName);
      }
    }

    И вот эти "конкретные" классы я хочу разместить в конфиге, в поле oftype.
    export const matchStorageConfig: IStorageConfig = {
      storageName: StorageName.Matches,
      oftype: MatchStorage,  // !!!!!!!! ВОТъ
      options: {
        keyPath: "id"
      },
      indexes: [
        {
          storageIndexName: "lobby_type_index",
          keyPath: "lobbyType",
        },
        {
          storageIndexName: "start_date_index",
          keyPath: "startDateTime",
        }
      ]
    };
    
    ....
    
    export const defaultDbConfig: IDbConfig = {
      version: 1,
      dbname: 'test123',
      storages: [
        matchStorageConfig,
      ]
    }
    Написано
  • Как типизировать переменную под хранение классов?

    johnymkp
    @johnymkp Автор вопроса
    Василий Банников, примерно так:
    export interface IStorage<T, K> {
      save(data: T | T[]): Promise<DbOperationResult<T>[]>;

    там несколько методов, все выглядят так же как save по сути.
    Написано
  • .stopPropagation() на самом деле не останавливает распространение события?

    johnymkp
    @johnymkp Автор вопроса
    Ivan Ustûžanin, отличные понятные ответы, большое спасибо!
    Написано
  • .stopPropagation() на самом деле не останавливает распространение события?

    johnymkp
    @johnymkp Автор вопроса
    Ivan Ustûžanin, а дефолтное действие должно выполняться только на цели, а не на каждом элементе через который проходит событие?

    Грубо говоря, допустим у тега при click было бы дефолтное действие вывести в консоль "Привет, мир!". И у нас было бы 5 div'ов, вложенных друг в друга. Мы щелкаем на самом внутреннем и событие начинает распространяться по этим div'ам. Тогда дефолтное действие должно выполниться только на "целевом" div? Т.е. мы увидим только одну надпись "Привет, мир!"?

    И если бы мы например повесили на самый внешний div обработчик с preventDefault, то мы бы таким образом сказали "не выполняй дефолтное действие на целевом div"? (Я-то поначалу думал, что вызов e.preventDefault() блокирует выполнение дефолтного поведения именно на текущем элементе, на котором обработчик сработал).
    Написано
  • .stopPropagation() на самом деле не останавливает распространение события?

    johnymkp
    @johnymkp Автор вопроса
    Т.е. вы хотите сказать, что браузер после stopPropagation действительно прекращает обходить элементы дальше, и просто вызывает дефолт-действие сразу на цели?
    Написано
  • Порождается ли событие в элементе или оно само по себе?

    johnymkp
    @johnymkp Автор вопроса
    Сергей Соколов, ваше объяснение мне нравится. Теперь однозначно понятно, что событие возникает только один раз. Браузер рассчитывает "конечную точку" этого события (кнопка, поле ввода и т.д.), создает объект события, а затем начинает обход дерева элементов до конечной точки и у каждого элемента проверяет, нет ли у него обработчика этого события. Если есть - он его вызывает.
    Написано
  • Порождается ли событие в элементе или оно само по себе?

    johnymkp
    @johnymkp Автор вопроса
    Aetae, это я понимаю, что объект, который попадает в обработчики - один и тот же.
    Вопрос-то в другом. Есть форма, а в ней разные поля ввода, чекбоксы и т.д. Мы вешаем обработчик на форму, вводим что-то в поле или переключаем чекбокс. Мы можем конечно сказать, что "Произошло событие change на чекбоксе". И в target у нас будет чекбокс, а в currentTarget - форма (если говорить конкретно про тот обработчик, который мы на форму повесили). Но ведь по сути-то сначала это событие произошло вовсе не на чекбоксе, оно произошло на window и просто самый глубокий элемент, до которого оно смогло распространиться - это чекбокс.
    Т.е. с технической точки зрения получается некорректно говорить, что "щелчок по чекбоксу спровоцировал событие".
    Написано
  • Порождается ли событие в элементе или оно само по себе?

    johnymkp
    @johnymkp Автор вопроса
    Lynn «Кофеман», не согласен про словоблудие. Скорее это попытка внести ясность в формулировки.
    Например, когда вы рассказываете про объект события, то как вы объясните, что такое target и currentTarget?
    Написано
  • Порождается ли событие в элементе или оно само по себе?

    johnymkp
    @johnymkp Автор вопроса
    Lynn «Кофеман», но можно ли говорить, что это событие "возникает" на каждом элементе по пути к кнопке? Вот, грубо говоря, сидишь на собеседовании и говоришь, "Щелкаем на кнопку. Событие сначала возникает на объекте Window, потом на объекте Document, и дальше оно возникает на всех объектах по пути к кнопке. Потом оно возникает на самой кнопке и идет обратно вверх, вновь возникая на каждом объекте до самого Window"?
    Я просто сам до терминологии дотошный, ментальную модель как это работает я уже составил, все понятно, но хочется еще и правильные слова подобрать.
    Написано