Можно ли эмулировать поведение полиморфного типа this
TypeScript, используя только интерфейсы вместо классов? Предположим, у меня есть такой код:
interface Foo {
foo(): this;
}
interface Bar extends Foo {
bar(): this;
}
function foo(this: Foo) { return this; }
function bar(this: Bar) { return this; }
function newFoo(): Foo {
return { foo };
}
function newBar(): Bar {
return { ...newFoo(), bar }; // Property 'bar' is missing in type 'Foo' but required in type 'Bar'.
}
Конструктор newBar()
не выполняет проверку типа, поскольку возвращаемый тип foo()
, полученный из деструктурированного newFoo()
, выводится как Foo
вместо Bar
.
Я обнаружил, что работает обратный метод упаковки миксинов:
function fooish<T>(object: T): T & Foo {
return { ...object, foo };
}
function newBar2(): Bar {
return fooish({ bar }); // Typechecks fine
}
Однако в некоторых ситуациях я бы предпочел первый метод, т. е. иметь простой конструктор, который просто возвращает Foo
вместо T & Foo
и компонует путем распространения свойств, а не приложения. По сути, я ищу наследование с интерфейсами, чтобы this
оставался набранным правильно.
Есть идеи?
Извините, если я не ясно выразился. Если у меня есть класс Foo
с методом foo
, который возвращает экземпляр Foo
обратно, т. е. return this
, и если я создаю его подкласс как Bar extends Foo
, то метод foo
на Bar
будет иметь возвращаемый тип Bar
(вместо Foo
). Я хотел бы сделать что-то подобное с интерфейсами. Не знаю, как это сформулировать в редакции.
• Вероятно, вы говорите, что хотите, чтобы foo()
и bar()
вернулись this
тогда. Не интерфейсам нужно что-то особенное (поскольку интерфейсы прекрасно поддерживают this
), а автономные функции, которые вы используете в качестве методов. Я был бы признателен, если бы вы могли отредактировать , чтобы сказать это, но если вы не можете понять, как это сделать, я могу через некоторое время внести свои собственные изменения. • Возможно этот подход может работать, когда вы используете дженерики для обработки необходимого полиморфизма (this
реализован как неявный дженерик-тип). Если да, то я напишу ответ; если нет, то чего не хватает?
Вероятно, я что-то упускаю, но не понимаю, в чем проблема с функциями, например. function newBar() { return { ...({} as Foo), bar }; };
показывает ту же ошибку.
О да, в примере с игровой площадкой вывод работает хорошо, возможно, я использую более старую версию TypeScript, попробую обновить и посмотреть, поможет ли это.
Чтобы {...({} as Foo), bar}
было Bar
, это as Foo
должно быть as Pick<Bar, "foo">
. Часть foo
в Foo
не обязательно должна соответствовать Bar
. Может быть, вы не понимаете, как работает полиморфный this
и что он означает? Единственный возможный способ обеспечить типобезопасность вашего кода — это если известно, что метод foo()
возвращает this
для текущего this
. Дело не в том, что Foo.foo
возвращается Foo
и Bar.bar
возвращается Bar
.
Хорошо, понятно, спасибо, что уделили время @jcalz, как всегда великолепно. Ваш ответ работает хорошо, немного раздражает, что мне приходится добавлять префикс к каждому методу, который возвращает this
, с общим префиксом, но я думаю, это имеет смысл. Напишите ответ и я одобрю его.
Я сделаю это, когда у меня будет возможность.
Вероятно, это произойдет не раньше, чем через несколько часов.
Полиморфный тип неявно является универсальным . Тип this
— это особый тип F-ограниченного полиморфизма , называемый « любопытно повторяющимся шаблоном шаблона в C++. Для реализатора типа this
— это «некоторый общий подтип текущего типа, который я пишу», тогда как для пользователь типа, this
является этим типом. Если вы хотите реализовать Foo
, вам необходимо убедиться, что метод foo()
возвращает любой подтип Foo
, который будет использоваться позже.
Ваш код по существу эквивалентен
interface Foo<T extends Foo<T>> {
foo(): T;
}
interface PlainFoo extends Foo<PlainFoo> {}
interface Bar<T extends Bar<T>> extends Foo<T> {
bar(): T;
}
interface PlainBar extends Bar<PlainBar> {}
function foo(this: PlainFoo) { return this; }
function bar(this: PlainBar) { return this; }
function newFoo(): PlainFoo {
return { foo };
}
function newBar(): PlainBar {
return { ...newFoo(), bar };
// Property 'bar' is missing in type 'Foo<PlainFoo>'
// but required in type 'Bar<PlainBar>'
}
где теперь, надеюсь, понятно, в чем проблема. Ваш newBar()
подразумевает возврат PlainBar
, но это Bar<PlainBar>
, то есть Foo<PlainBar>
, и поэтому метод foo()
должен возвращать PlainBar
. Но вместо этого он возвращает PlainFoo
. Тип this
должен сужаться в подклассах, вот в чем вся суть.
Итак, чтобы ваш код работал с
interface Foo { foo(): this; }
interface Bar extends Foo { bar(): this; }
Вам нужны foo
и bar
, чтобы вернуть this
, а не Foo
и Bar
. Это означает, что универсальный тип this
должен быть явно представлен как параметр универсального типа:
function foo<T extends Foo>(this: T) { return this; }
function bar<T extends Bar>(this: T) { return this; }
И теперь все работает (и вы также не хотите аннотировать возвращаемый тип newFoo()
как Foo
, поскольку это забивает общий:
function newFoo() {
return { foo };
}
function newBar(): Bar {
return { ...newFoo(), bar };
}
Теперь все это работает, поскольку полиморфная природа this
сохраняется.
Я этого не понимаю.
newFoo
возвращаетFoo
, а неthis
. ВнутриBar
вам нужен методfoo()
, чтобы вернутьBar
, а неFoo
, посколькуthis
теперьBar
. Я не понимаю, чего вы пытаетесь достичь и как то, что вы делаете, должно быть типобезопасным. Где конкретно вы пытаетесь «подражать» типуthis
? Я имею в виду, что вы используетеthis
в своем коде, так что, по-видимому, на самом деле он должен использовать тип, а не эмулировать его. Я здесь безнадежно запутался. Не могли бы вы отредактировать, чтобы уточнить?