Проблема с условными типами Typescript с типом «this» в контексте класса

#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, я добавил еще один пример к своему вопросу, это может быть понятнее, я углубляюсь в проблемы, с которыми я сталкиваюсь с условным типом и этим типом.