Объединить интерфейс из const с универсальной c функцией в универсальный c интерфейс - PullRequest
0 голосов
/ 25 апреля 2020

Учитывая кучу унаследованного кода, следующего за шаблоном с const, подобным этому:

const fnUsedInSetPrototypeOf = {
    equalityComparer<T>(a: T, b: T) { return a === b },
    otherFn<T> (this: T) { /*...*/ },
    // ... other things, all along the lines of `method<T> (...)`
} as const

Я хотел бы знать, как объединить typeof fnUsedInSetPrototypeOf в универсальный c интерфейс, который «сводит» все функции к типу интерфейса c, расширяющему его. например,

type Proto = typeof fnUsedInSetPrototypeOf

interface SuperProto<T> extends Proto {
    equalityComparer (a: T, b: T): boolean
}

Это значительно ускорит и упростит добавление типов в существующую кодовую базу Javascript (Knockout / TKO).

Однако выдает ошибку:

Тип (a: T, b: T) => boolean нельзя назначить типу <T>(a: T, b: T) => boolean.

Я пробовал различные сопоставления типов, например type Proto<T> = { [key in keyof typeof fnUsedInSetPrototypeOf]: fnUsedInSetPrototypeOf<T>[key]?? }, чтобы установить / передать свойство generi c, но ничего еще не сработало.

Реальная проблема, которую я пытаюсь решить, немного сложнее, а именно множественное наследование fnUsedInSetPrototypeOf и SuperProto<T>, как это проявляется в типе Knockout Observable в библиотеке Knockout / TKO.

У меня есть Детская площадка , которая иллюстрирует проблему (среди различных попыток ее исправить).

Редактировать:

Вторая Детская площадка :

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

interface SuperProto<T> extends Omit<Proto, keyof SuperProto<T>> {
    equalityComparer (a: T, b: T): boolean
    // equalityComparer<T> (a: T, b: T): boolean
}

// This should not give an error
const x = (v: SuperProto<number>) => v.otherFn(123)
const x2 = (v: SuperProto<number>) => v.otherFn2(123)

// This should error with argument to `otherFn` not being a number
const y = (v: SuperProto<number>) => v.otherFn('abc')
const y2 = (v: SuperProto<number>) => v.otherFn2('abc')

1 Ответ

2 голосов
/ 27 апреля 2020

Давайте воспользуемся этим определением 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 ссылка на код

...