Передача абстрактного класса в качестве параметра в Typescript - PullRequest
0 голосов
/ 28 января 2020

Я пытаюсь настроить класс, который динамически создает строку фильтра. Для этого я создал следующий класс:

export class UrlBuilder<T extends BaseSpalten> {

    private spalten: T;

    ....

    public filterData(config: (spalten: T, addParameter: (key: string, value: string) => void,
        addParameterListe: (key: string, value: string[]) => void) => void): UrlBuilder<T> {
        config(this.spalten, this.addQueryParameter, this.addQueryParamterList);
        return this;
    }

    ....
}

Но теперь у меня есть проблема в том, что класс "BaseSpalten" и расширенные классы являются классами c:

export class BaseSpalten {
    public static AenderungVersion: string = 'AenderungVersion';
    public static Dirty: string = 'Dirty';
}

Но мне нужны свойства классов внутри функции config, чтобы я мог получить действительный фильтр. Как передать ссылку на статистику c T в функцию конфигурации?

Вот пример ссылки на игровую площадку для лучшего понимания

1 Ответ

1 голос
/ 28 января 2020

Насколько я могу судить, мы можем свести ваш вопрос к следующему. Учитывая иерархию классов с некоторыми свойствами stati c:

class BaseClass {
    public static staticPropBase: string = 'foo';
}

class SubClass extends BaseClass {
    public static staticPropSub: string = 'bar';
}

Как мы можем получить доступ к этим свойствам stati c из экземпляров класса? Во время выполнения это на самом деле просто; все JavaScript объекты имеют свойство constructor , которое ссылается на функцию конструктора, которая его создала. Таким образом, (new SubClass()).constructor будет SubClass со всеми его свойствами c.

Таким образом, вы можете написать это, и это будет работать:

const subclass = new SubClass();
console.log(subclass.constructor.staticPropBase); // TS error, but "foo" at runtime
console.log(subclass.constructor.staticPropSub);  // TS error, but "bar" at rntime

Но проблема в том, что компилятор TypeScript несчастен. Он не понимает, что subclass.constructor является конструктором SubClass. Все, что он знает о subclass.constructor, это то, что это Function. Это правда (конструкторы класса являются функциями), но не указывают c достаточно, чтобы быть полезным.

Но подождите, разве компилятор TypeScript не должен знать , что тип (new SubClass()).constructor - это typeof SubClass само по себе? Да, вероятно ... и это давняя открытая проблема, см. Microsoft / TypeScript # 3841 . Заявленная причина , почему это не делается автоматически, заключается в том, что конструкторы подклассов не обязаны быть действительными подтипами своих конструкторов суперкласса. В следующем сценарии:

class A {
    a: string = "a";
    constructor() { } // no param
}

class B extends A {
    b: number;
    constructor(b: number) { // one param 
        super();
        this.b = b;
    }
}

, хотя тип экземпляра B является подтипом типа экземпляра A, его конструктор typeof B является , а не подтипом typeof A (поскольку конструктор B требует аргумент, а A - нет):

const a: A = new B(123); // okay
const _A: typeof A = B; // error! Type 'typeof B' is not assignable to type 'typeof A'.(2322)

Если компилятор TypeScript автоматически предоставил A constructor свойство типа typeof A и B a constructor свойство типа typeof B, тогда типы экземпляров B больше не будут действительным подтипом A, и модель подтипа extends не будет работать.

Blecch. Было бы неплохо, если бы TS мог сделать что-то лучше, чем Function. Но даже если они в конце концов внесут изменения, у вас есть проблема сегодня . Что вы можете сделать сейчас?


Вероятно, лучшим текущим решением является ручное объявление о том, что экземпляры вашего класса имеют свойство constructor правильного типа. Поскольку ваши конструкторы классов пусты, typeof SubClass является допустимым подтипом typeof BaseClass, поэтому вышеупомянутая проблема не возникает. (Если это произойдет, вы, вероятно, могли бы использовать такой тип, как Pick<typeof SubClass, keyof typeof SubClass>, чтобы сохранить свойства stati c, но игнорировать сигнатуру конструктора. Дополнительная информация доступна по запросу.)

Объявление немного странное , но выглядит это так:

class BaseClass {
    declare ["constructor"]: typeof BaseClass; // TS 3.7+
    //["constructor"]!: typeof BaseClass; // TS 3.6-
    public static staticPropBase: string = 'foo';
}

class SubClass extends BaseClass {
    declare ["constructor"]: typeof SubClass; // TS 3.7+
    //["constructor"]!: typeof SubClass; // TS 3.6-
    public static staticPropSub: string = 'bar';
}

(Обратите внимание, что для TS3.7 и выше вы должны использовать a declare модификатор свойства , тогда как для TS2.7 - TS3.6 Вы должны использовать утверждение определенного присваивания . Они в основном делают одно и то же. Больше информации по запросу.)

И теперь просто работает следующее:

const subclass = new SubClass();
console.log(subclass.constructor.staticPropBase); // okay, "foo"
console.log(subclass.constructor.staticPropSub);  // okay, "bar"

В противном случае вы всегда можете просто использовать утверждения типа для подавления предупреждений компилятора:

console.log((subclass.constructor as typeof SubClass).staticPropBase); // okay, "foo"
console.log((subclass.constructor as typeof SubClass).staticPropSub);  // okay,  "bar" 

Лично я бы довольно сильно ввел определение класса, но утверждения типа также будут работать.


Хорошо, надеюсь, это поможет; удачи!

Детская площадка ссылка на код

...