Можно ли эмулировать поведение полиморфного типа 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в своем коде, так что, по-видимому, на самом деле он должен использовать тип, а не эмулировать его. Я здесь безнадежно запутался. Не могли бы вы отредактировать, чтобы уточнить?