Извлечь тип из анонимного универсального класса TypeScript

#typescript

#typescript

Вопрос:

Кажется, я не могу определить наиболее элегантный способ определения типа анонимных универсальных классов, которые генерируются и возвращаются из функций.


Вот фрагмент, который содержит функцию, которая генерирует анонимные классы, и одну из функций, содержащихся внутри.

Общие элементы необходимы для того, чтобы TypeScript предоставлял подсказки о типе возвращаемого значения и свойстве параметра при использовании функции get.

 export function createController<T, U extends { id?: number }>(cls: new () => T) {
    return class extends BaseController {
        public constructor() {
          super(cls.name.split(/(?=[A-Z])/).join('_').toLowerCase());
        }

        public async get(params: U): Promise<T | null> {
          try {
            const { data } = params.id ? await this.http.get(`${this.base}-${params.id}`) : await this.http.get('', { params });
            return deserialize(cls, search(data, 'data'));
          } catch (err) {
            return null;
          }
        }
    };
}

  

Проблема возникает, когда я пытаюсь фактически создать и сохранить результат этого.


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

 export interface UserQueryParams {
    id?: number;
    username?: string;
}

const UserController = createController<User, UserQueryParams>(User);
  

Теперь экземпляры UserController можно создавать, просто создавая их как обычные классы через new UserController() ;

При их непосредственном сохранении проблем нет, т. е

 // Correctly inferred as createController<User, UserQueryParams>.(Anonymous class)
const controller = new UserController();
  

Однако, скажем, я хотел создать класс, содержащий эти контроллеры:

 class Client {
    public controller: ???

    public constructor() {
        this.controller = new UserController();
    }
}
  

Кажется, я не могу найти наилучший способ ввести свойство.

Использование public controller: UserController ошибок, потому что UserController это значение, а не тип.

Использование public controller: typeof UserController также, похоже, не работает, затем назначение конструктора завершается ошибкой из-за несовместимых типов.

Использование public controller: typeof UserController.prototype ПОЧТИ работает, но информация о типе теряется. Это выводится как createController<any, any>.(Anonymous class) . Два any , используемые для обобщений, означают, что параметр и информация о типе возвращаемого значения отсутствуют.

Используя

 const temp = new UserController();

// In the class..

public controller: typeof temp;
  

Это работает! Однако крайне некрасиво создавать временные экземпляры КАЖДОГО контроллера, который я мог бы сгенерировать.

Есть ли какой-либо другой способ ввести controller правильно?

Ответ №1:

Я нашел решение, которое считаю достаточно элегантным, используя InstanceType из TypeScript 2.8

 import { UserController } from '@/controllers';

class Client {
    public controller: InstanceType<typeof UserController>;

    public constructor() {
        this.controller = new UserController();
    }
}
  

Я даже могу экспортировать тип из того же файла, используя то же имя, что и сгенерированный UserController класс, и TypeScript, похоже, может сделать вывод, что экспортируются как тип, так и класс.

 // Inside './controllers/index.ts
export const UserController = createController<User, UserQueryParams>(User);

export type UserController = InstanceType<typeof UserController>;

// ... In some other file

// TypeScript understands you are importing the Type AND the Class!
import { UserController } from './controllers';

class Client {
    public controller: UserController; // Works as type!

    public constructor() {
        this.controller = new UserController() // Works as class!
    }
}