GOOGLE ADS

суббота, 30 апреля 2022 г.

Сигнатура для функции базового класса, которая является оболочкой для функций производного класса

Я определяю _wrapметод базового класса, который изменяет методы производных классов.

class Base {
//this is the signature I'm working out
_wrap(key: unknown) {
const fn = this[key]
//I want typeof fn to be "Function"
//which is not callable
fn()
}
}
class Derived extends Base {
constructor() {
super()
//I want _wrap1 to require "isAFunction1 | isAFunction2"
super._wrap("isAFunction1")
}
isAFunction1() { }
isAFunction2() { }
notAFunction = "some value"
}

Я изо всех сил пытаюсь написать подпись типа этого метода, чтобы он:

  • Предотвращает вызовы _wrapиз производных классов с ключами, которые не соответствуют функциям в производном классе.

  • Позволяет мне использовать this[key]внутри _wrapкак соответствующий тип без приведения

  • Я могу использовать _wrap(key: keyof typeof this)для достижения № 1, но не № 2.

    Я чувствую, что этот связанный вопрос должен работать. Я не понимаю, почему, но это не соответствует ни одному из требований — см. кодовую площадку здесь.

    type KeysMatching<T, V> = {[K in keyof T]-?: T[K] extends V? K: never}[keyof T];
    class Base {
    _wrap(key: KeysMatching<typeof this, Function>) {
    const fn = this[key]
    //I expect typeof fn to be "Function", but it's not, it's this[KeysMatching<this, Function>],
    //which is not callable
    fn()
    }
    }
    class Derived extends Base {
    constructor() {
    super()
    //I expect _wrap1 to expect "isAFunction1 | isAFunction2", but it's not, it's
    //this[KeysMatching<this, Function>], which doesn't match string
    this._wrap("isAFunction1")
    }
    isAFunction1() { }
    isAFunction2() { }
    notAFunction = "some value"
    }

    Есть ли способ написать эту подпись для достижения обеих этих целей?

    (Примечание: это, вероятно, не идеальный код. Я аннотирую существующую библиотеку и не вношу изменения в код.)


    Решение проблемы

    Проблема с использованием KeysMatching<T, V>заключается в том, что компилятор недостаточно умен, чтобы понять, что T[KeysMatching<T, V>]можно присвоить V. Это функция типа, включающая условные типы для параметров универсального типа, которые компилятор в основном просто откладывает и считает непрозрачными. Для получения дополнительной информации см. Microsoft/TypeScript#30728.

    С другой стороны, компилятор достаточно умен, чтобы понять, что Record<K, V>[K](эквивалентно {[P in K]: V}[K]) можно присвоить V... это общий индексированный доступ к сопоставленному типу, без каких-либо условных типов.

    Таким образом, это означает, что вы можете попытаться ограничить thisдля Record<K, () => void>общего ключа K, соответствующего типу key, вместо ограничения самого типа key.

    Обратите внимание, что я использую () => void выражение типа функции вместо типаFunction; первая - это функция, которую можно (относительно) безопасно вызывать с нулевыми аргументами, а вторая - нетипизированный вызов функции, который не защищает от неправильного использования типов параметров. Если fnесть (x: string) => x.toUpperCase(), то звонить совсем не хочется fn(). Если вы проверите против Record, вы не получите предупреждения компилятора, и проблема будет рассматриваться только как ошибка времени выполнения. Если вы проверите против () => void, компилятор будет жаловаться, что (x: string) => stringне может быть назначен () => void.

    В любом случае, эта сигнатура вызова может выглядеть так:

    class Base {
    _wrap1<K extends PropertyKey>(this: this & Record<K, () => void>, key: K) {
    const fn = this[key].bind(this) // <-- bound
    fn()
    }
    }

    Я использую thisпараметр, чтобы сообщить компилятору, что вы можете вызывать только foo._wrap1()объект, fooназначаемый this & Record<K, ()=>void>( пересечение: типаthis, соответствующего типу текущего производного подкласса; иRecord<K, ()=>void>.

    О, еще в сторону. Код вашего примера просто вызывает fn()без предварительной привязки к this. Это может быть большой проблемой для любой реализации метода, который разыменовывает файлы this. И, к сожалению, TypeScript не сможет легко отловить эти ошибки, см. microsoft/TypeScript#7968 для получения дополнительной информации. Поэтому я написал this[key].bind(this), чтобы избежать ошибок во время выполнения.

    Давайте проверим это:

    class Derived extends Base {
    constructor() {
    super()
    this._wrap1("isAFunction1") // okay
    this._wrap1("isAFunction2") // okay
    this._wrap1("notAFunction") // error!
    //~~ <-- types of property 'notAFunction' are incompatible.
    }
    isAFunction1() { console.log(this.notAFunction + " says hello") }
    isAFunction2() { console.log(this.notAFunction + " says goodbye") }
    notAFunction = "some value"
    }
    new Derived()
    // some value says hello
    // some value says goodbye
    //

    Комментариев нет:

    Отправить комментарий

    Laravel Datatable addColumn returns ID of one record only

    Я пытаюсь использовать Yajra Datatable для интеграции DataTable на свой веб-сайт. Я смог отобразить таблицу, но столкнулся с проблемой. В по...