打字稿:为什么 class 泛型不能分配给从“this”推断出的相同泛型?

Typescript: why is a class generic not assignable to same generic inferred from `this`?

给定如下代码,为什么Typescript在getInferred方法中出错?是否存在 ValueOf<this>T 可能不同的情况?

interface Wrapper<T> {
    value: T;
}

type ValueOf<T> = T extends Wrapper<infer U> ? U : never;

class Foo<T> implements Wrapper<T> {
    value: T;

    constructor(value: T) {
        this.value = value;
    }

    getInferred = (): ValueOf<this> => {
        // Type 'T' is not assignable to type 'GetGeneric<this>'.
        return this.value;
    }

    getSimple = (): T => {
        // Works Fine
        return this.value;
    }
}

对于我的用例,我正在向 class 动态添加方法,ValueOf<this> 为动态方法提供更好的 return 类型。

const mixin = {
    getFooInferred<Self extends Foo<any>>(this: Self) {
        return this.getInferred();
    },
    getFooSimple<Self extends Foo<any>>(this: Self) {
        return this.getSimple();
    }
}

function makeFooWithMixin<T>(value: T) {
    const foo = new Foo(value);

    Object.defineProperties(foo, {
        getFooInferred: {
            value: mixin.getFooInferred,
        },
        getFooSimple: {
            value: mixin.getFooSimple,
        }
    });

    return foo as Foo<T> & typeof mixin;
}

const foo = makeFooWithMixin("hello")

// When using the returntype of `getInferred`, we correctly get `string` as the type here
const resultInferred = foo.getFooInferred()

// When using `getSimple`, we instead get `any` because `getFooSimple` types the `Self` generic as `Foo<any>`
const resultSimple = foo.getFooSimple();

Typescript playground link for all above code

polymorphic this type is implemented as an implicit generic type parameter that all classes and interfaces have (see microsoft/TypeScript#4910)。而你的 ValueOf<T> 类型,定义为

type ValueOf<T> = T extends Wrapper<infer U> ? U : never;

是一个conditional type。所以 ValueOf<this> 是一个 条件类型 取决于 通用类型参数 .

不幸的是,TypeScript 编译器无法对可分配给此类类型的值进行太多推理。它 延迟 类型的评估,并且只有在指定 this 后才能知道它到底是什么,例如在调用 new Foo("x").getInferred() 中,其中 this将是 Foo<string>。在 getInferred() 的内部,this 是未指定的(它可以是 Foo<T> 的任何子类型),因此 ValueOf<this> 本质上是 opaque 给编译器。并不是 this.value 可以是 ValueOf<this> 以外的类型,而是编译器看不到它。它将拒绝任何尚未属于 ValueOf<this>.

类型的值

如果你使用像 this.value as ValueOf<this> 这样的 type assertion,那么编译器将允许你 return,但这只是因为你声称 this.value 是类型ValueOf<this>,而不是因为编译器可以区分一种方式:

getInferred = (): ValueOf<this> => {
    return this.value as ValueOf<this>; // okay
}

一般来说,如果您需要提供通用条件类型的值,您将不得不做一些不安全的事情,比如类型断言。但在这个特定的例子中,你有一个选择。您使用 ValueOf<T> 所做的就是在 T 中查找 value 键控的 属性。这可以在没有条件类型的情况下完成。您可以使用 indexed access type 代替:

type ValueOf<T extends Wrapper<any>> = T['value']

即使编译器仍然不擅长理解泛型类型的任意操作,它确实知道如果你有一个 T 类型的值和一个 K 类型的键即使 TK 是通用的,您在该键处读取的 属性 值也是 T[K] 类型。所以它应该能够验证 this.value 是类型 this["value"]:

getInferred = (): ValueOf<this> => {
    return this.value; // okay
}

确实可以。

Playground link to code