#typescript
#typescript
Вопрос:
я пытаюсь использовать условные типы с типом «this» в контексте класса.
я кратко изложил свои проблемы в следующих примерах:
type Foo<T> = T extends string ? T : never;
// 'Foo' conditional type don't pass with 'this' Type
class A {
public val: string = 'bar'
public getOtherVal(): Foo<this['val']> {
return 'baz' // error: Type 'string' is not assignable to type 'Foo<this["val"]>'.
}
}
// 'Foo' conditional type pass with class Type (here 'B')
class B {
public val: string = 'bar'
public getOtherVal(): Foo<B['val']> {
return 'baz'
}
}
// Typing with 'this' Type and without the conditional Type pass
class C {
public val: string = 'bar'
public getOtherVal(): this['val'] {
return 'baz'
}
}
Примеры на игровой площадке typescript => здесь
Я действительно не понимаю, почему первый не передает «этот» тип? Этот тип действительно удобен, когда вам нужно определять абстрактные классы, но эта проблема глобально блокирует его использование с условными типами… может быть, я что-то пропустил, но я открыт для некоторых идей, чтобы исправить это или другие методы?
спасибо за вашу помощь!
Редактировать , чтобы быть более точным в вопросе:
Основная идея в моем контексте — использовать этот тип, чтобы не фиксировать тип класса, что позволяет добавлять некоторые ограничения на некоторые абстрактные методы или свойства класса, в зависимости от его реализаций.
Я проиллюстрировал проблему в другом примере :
type Foo<T> = T extends string ? T : (number | boolean);
// Not pass with conditional type:
// Abstract declaration
abstract class A {
public abstract val: string | number | boolean
// function return a value depending of class property value.
public abstract getOtherVal(): Foo<this['val']>
}
// Implementation of A class
class Aa implements A {
public val: string = 'bar';
// Return type constraint by the type of val.
public getOtherVal() { // error: ...Type 'string' is not assignable to type 'Foo<this["val"]>'.
return 'baz'
}
}
// Pass without conditional type:
// Abstract declaration
abstract class B {
public abstract val: string | number | boolean
// function return a value depending of class property value.
public abstract getOtherVal(): this['val']
}
// Implementation of B class
class Bb implements B {
public val: string = 'bar';
// Return type constraint by the type of val.
public getOtherVal() {
return 'baz'
}
}
На Typescript playground => здесь
Комментарии:
1. Почему бы просто не использовать Foo<B[‘val’]> ?
2. Потому что, как я объяснил в своем add, это исправляет тип класса, которого я бы хотел избежать
Ответ №1:
Это правильное поведение. По крайней мере, когда это относится к классам A и B в вашем примере. На самом деле это класс C, который ведет себя неправильно. Позвольте мне объяснить.
Во-первых, (из официальных документов) полиморфный this
тип представляет тип, который является подтипом содержащего класса или интерфейса.
Рассмотрим следующее:
// Your original class C
class C {
public val: string = 'bar'
public getOtherVal(): this['val'] {
return 'baz' // no warning, no error!
}
}
class C1 extends C {
val: 'a' | 'b';
// perfectly OK, as 'a' | 'b' extends string
// (and the type of val field in superclass C was string).
}
class C2 extends C {
val: number;
// produces Error:
// Property 'val' in type 'C2' is not assignable to the same property in base type 'C'.
// Type 'number' is not assignable to type 'string'.ts(2416)`.
}
const val = new C1().getOtherVal();
// val constant is of type 'a' | 'b', not string. But actually it holds string 'baz'.
Тип this[‘val’]
может быть любым подтипом string
. Поэтому не должно быть разрешено возвращать string
, когда this[‘val’]
ожидается, поскольку это может быть ограничено в подклассах. Это то, что делает класс A, и это совершенно нормально. Напротив, класс C позволяет возвращать string
ожидаемый тип this[‘val’]
. Итак, это класс C, который ведет себя неправильно, а не класс A.
Вообще говоря, this
возвращаемый тип as позволяет вам возвращать тип подкласса this из унаследованных методов без каких-либо изменений. Это не сокращение от длинного имени класса!
class Fluent {
doSomething(): this { /* ... */ return this; }
doSomethingElse(): this { /* ... */ return this; }
}
class Subclass extends Fluent {
doInSubclass(): this { /* ... */ return this; }
};
const instance: Subclass = new Subclass().doSomething().doInSubclass().doSomethingElse();
Комментарии:
1. Но как насчет ошибки, вызванной использованием условного типа Foo и этого типа в классе A ? На самом деле это моя главная проблема
2. B — это просто пример, иллюстрирующий, что использование условного типа проходит без использования этого типа. И C, чтобы проиллюстрировать, что «Этот» тип будет возвращать правильный тип без применения к нему условного типа.
3. @Nicolas Rs это правильное поведение. Тип this[‘val’] может быть любым подтипом string . Поэтому он не должен позволять возвращать какую-либо строку, поскольку это может быть ограничено в подклассах. Это то, что делает класс A, и это совершенно нормально. Класс C позволяет возвращать любую строку, в которой ожидается тип this[‘val’] . Итак, это класс C, который ведет себя неправильно, а не класс A.
4. Хорошо, я лучше понимаю, если я последую за вами, ввод C не должен быть разрешен ts теоретически?
Ответ №2:
Я не знаю, почему реализация класса B недостаточно хороша для вас … но не InstanceType
решает вашу проблему?
// 'Foo' conditional type don't pass with 'this' Type
class A {
public val: string = 'bar'
public getOtherVal(): Foo<InstanceType<typeof A>['val']> {
return 'baz'
}
}
но, честно говоря, если я делаю что-то подобное, я бы предпочел, чтобы это было организовано как:
type Foo<T> = T extends string ? T : never;
type Value = string;
class A {
public val: Value = 'bar'
public getOtherVal(): Foo<Value> {
return 'baz'
}
}
Комментарии:
1. На самом деле не совсем, потому что в вашем решении вы исправляете тип класса A, я добавил еще один пример к своему вопросу, это может быть понятнее, я углубляюсь в проблемы, с которыми я сталкиваюсь с условным типом и этим типом.