Давайте воспользуемся этим определением Proto
:
const fnUsedInSetPrototypeOf = {
equalityComparer<T>(a: T, b: T) { return a === b },
otherFn<T>(this: SuperProto<T>, v: T) { /*...*/ },
otherFn2<T, U>(this: T, v: U) { /*...*/ },
} as const
type Proto = typeof fnUsedInSetPrototypeOf
Вы хотите определить SuperProto<T>
так, чтобы параметр generi c вышел из методов и поднялся в SuperProto<T>
сам по себе, так что вместо специфицированного c типа, где каждый метод является независимо generi c, у вас есть один универсальный тип c, где каждый метод определен c. Что-то вроде:
interface SuperProto<T> {
equalityComparer(a: T, b: T): boolean
otherFn(v: T): void
otherFn2(v: T): void;
}
Теперь вы не можете объявить это SuperProto<T> extends Proto
. Такое объявление потребует, чтобы SuperProto<T>
был подтипом Proto
; любой экземпляр SuperProto<T>
должен использоваться как Proto
. Но ваши определения подразумевают, что верно обратное: Proto
на самом деле является подтипом SuperProto<T>
для любого T
по вашему выбору. Компилятор может легко распознать это отношение; если вы присвоите значение типа Proto
переменной типа, скажем, SuperProto<number>
, ошибки не будет:
const superProtoNum: SuperProto<number> = fnUsedInSetPrototypeOf; // okay
const superProtoStr: SuperProto<string> = fnUsedInSetPrototypeOf; // okay
Таким образом, одним из решений здесь является исключение extends Proto
в определение SuperProto<T>
, и пусть компилятор обрабатывает проверку структурной совместимости между типами:
const x = (v: SuperProto<number>) => v.otherFn(123) // okay
const x2 = (v: SuperProto<number>) => v.otherFn2(123) // okay
const y = (v: SuperProto<number>) => v.otherFn('abc') // error
const y2 = (v: SuperProto<number>) => v.otherFn2('abc') // error
Конечно, это означает, что вам нужно будет полностью указать методы и свойства SuperProto<T>
, упоминая каждый метод и имя параметра дважды. Если бы Proto
имел некоторые методы или свойства, чьи типы должны оставаться одинаковыми, вы могли бы сохранить некоторые записи, объявив
interface SuperProto<T> extends Omit<Proto, keyof SuperProto<T>> {
equalityComparer(a: T, b: T): boolean
otherFn(v: T): void
otherFn2(v: T): void;
}
и SuperProto<T>
автоматически унаследует любые свойства / методы от Proto
, которые не являются не упоминается явно в SuperProto<T>
. Но поскольку, похоже, ваше определение не имеет таких неизменных методов / свойств, Omit<Proto, keyof SuperProto<T>>
оценивается как {}
, и, поскольку все типы объектов являются подтипами {}
, нет смысла писать это; просто оставьте это.
Ясно, что вы надеялись, что будет какой-то способ представить отношения между Proto
и SuperProto<T>
в сжатой форме, и компилятор выполнит работу по переводу между ними. К сожалению, в настоящее время нет такого синтаксиса в TypeScript. То есть, учитывая следующие типы:
type Foo = <T>(x: T) => T;
type Bar<T> = (x: T) => T;
нет способа определить одно в терминах другого. Типы Foo
и Bar<T>
связаны между собой, но отличаются тем, что их общие параметры c количественно определяются по-разному. Вы можете думать о Foo
как о пересечении Bar<T>
для каждого возможного T
. Или, возможно:
type Foo = <T> Bar<T>
Но это неверный синтаксис в TypeScript. Есть открытое предложение, microsoft / TypeScript # 17574 , для поддержки "значений generi c", которые действуют как пересечение всех возможных спецификаций типа generi c. В настоящее время универсальные c типы функций, такие как Foo
, являются единственными такими универсальными значениями c, которые существуют в TypeScript и имеют собственный синтаксис. Если значения generi c полностью поддерживаются, вы сможете получить Foo
из Bar
и многое другое. И в такой гипотетической версии TypeScript вы вполне могли бы иметь возможность express SuperProto<T>
и Proto
некоторым менее избыточным способом. Но пока это невозможно, и лучшее, что вы сможете сделать, - это полностью выписать generi c -type-with-speci c -methods и конкретизировать c -type-with-generi c -методы явно.
Хорошо, надеюсь, это поможет; удачи!
Playgound ссылка на код