Я определяю _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
//
Комментариев нет:
Отправить комментарий