为什么在推断“ this”类型与显式接收“ this”类型时,此映射/条件类型的行为会有所不同?

时间:2018-08-21 00:49:58

标签: typescript typescript2.8

考虑以下代码,该代码使用TypeScript语言功能introduced in v2.8(条件类型):

type P<TObject, TPropertySuperType> = {
    [K in keyof TObject]: TObject[K] extends TPropertySuperType ? K : never;
}[keyof TObject];

function g<
    T,
    K extends keyof Pick<T, P<T, string>>
    >(obj: T, prop: K): void { }

class C {
    public alpha: string;
    public beta: number;

    public f(): void {
        g(this, "alpha"); // <-- does not compile!
        g(this, "beta");

        g<C, "alpha">(this, "alpha");
        g<C, "beta">(this, "beta");

        g(new C(), "alpha");
        g(new C(), "beta");

        this.g2("alpha");
        this.g2("beta");

        this.g2<"alpha">("alpha");
        this.g2<"beta">("beta");
    }

    public g2<
        K extends keyof Pick<C, P<C, string>>
        >(prop: K) { }
}

类型P背后的想法是,它选择TObject的属性的名称,这些属性满足以下条件:该属性扩展了TPropertySuperType。然后,函数gg2在类型参数约束中使用类型P,例如:

  • 仅当g参数是prop类型的extends string类型的属性的名称时,才能调用obj
  • 仅当g2参数是prop类型extends string类型的属性的名称时,才能调用C

在这里,因为C.alpha的类型为string,而C.beta的类型为number,所以我希望g / {{1 }},使用g2进行编译,而所有五个使用prop === "alpha"的调用 not 进行编译。

但是,调用prop === "beta"不会 进行编译,因为您可以看到是否将此代码粘贴到the TypeScript playground中。错误是:

g(this, "alpha")

为什么这个特定的调用失败?我猜想这与TypeScript如何推断Argument of type '"alpha"' is not assignable to parameter of type 'this[keyof this] extends string ? keyof this : never'. 的类型有关,但是细节对我来说很模糊。

2 个答案:

答案 0 :(得分:2)

我同意arthem,最可能的罪魁祸首是多态this。虽然很明显this的类型将是多态的this。尽管您可以肯定地说C['alpha']的类型为string,但对于this['alpha']却不能这样说,您只能说this['alpha'] extends string这是一个更为复杂的关系供编译器遵循。不确定这种类比是否有帮助,但是多态this的作用类似于该类的隐藏类型参数,并且使用它也有类似的限制。例如,在g内,obj['prop']的类型又一次不知道是string,这是因为对通用类型参数的说法存在局限性

function g<
    T,
    K extends keyof Pick<T, P<T, string>>
    >(obj: T, prop: K): void {  obj[prop].charAt(0) /*error*/}

尽管上面只是猜测(我会有点模糊),但解决上述错误的解决方案将解决this的问题,即提出我们的约束,即只有string键可以以不同的方式被传递。

function g3<
    K extends string | number | symbol,
    T extends Record<K, string>
    >(obj: T, prop: K): void {   obj[prop].charAt(0) /* ok*/ }

class C {
    public alpha!: string;
    public beta!: number;

    public f(): void {
        g3(this, "alpha"); // also ok as expected
        g3(this, "beta"); //not ok
    }
}

答案 1 :(得分:1)

很难不看编译器的源代码就很难分辨,但是有一点很清楚:this内的C没有类型C,基于这样的观察,如果您添加类型断言this as C

class C {
    public alpha: string;
    public beta: number;

    public f(): void {
        g(this as C, "alpha"); // ok

错误消息中为this[keyof this]提示的类型TObject[K]暗示this"polymorphic this"类型-表示{{1 }}。

我不知道是什么导致C在这里被视为多态的(也许总是假定它是多态的),但是不幸的是,它的键集不是一成不变的。

尽管可以证明,即使使用了多态,this仍将是其键之一,并且"alpha"类型将与this["alpha"]兼容,如在{{1}中声明的那样},编译器不会遵循必要的逻辑,只是安全地运行-“如果不是从静态上知道它是不兼容的”。