类方法的隐式`this`类型

时间:2017-03-22 16:45:26

标签: typescript

编辑:看来这是Typescript中的一个已知问题。一个solution was once implemented,但最终由于无法解决的性能问题而被拉扯。

这种情况通常出现在我们的代码库中:

<?php
     $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
    ?>

Typescript中的解决方案是:

function consumer<T>(valueProducer: () => T) {
    let value = valueProducer();
    console.log(value);
}

class Foo {
    private _value: number = 100;

    getValue(): number {
        return this._value;
    }

    constructor() {
        // Oops! Inside consumer(), getValue will be called with wrong this
        consumer(this.getValue);
    }
}

或者这个:

consumer( () => this.getValue() ); // capture correct this

这个问题对于一个Typescript / Javascript程序员来说可能是显而易见的,但是我们的团队正在将大量的C#移植到Typescript,而在C#中这是而不是一个错误(即在C#中传递的方法)自动绑定到对象实例)。所以我希望类型系统能够捕获此错误,如果可能的话。

明确键入回调中使用的consumer( this.getValue.bind(this) ); // bind to correct this 的第一个明显的步骤:

this

我希望这足够了,但事实证明我还需要在Foo方法上显式键入function consumer<T>(valueProducer: (this: void) => T) { let value = valueProducer(); console.log(value); } 参数:

this

有了这两件事,我现在得到了我想要的错误:

class Foo {
    getValue(this: Foo): number { // I could also have written getValue(this: this)
        return this._value;
    }

但是,我不想在我的应用中为每个方法添加error TS2345: Argument of type '(this: Foo) => number' is not assignable to parameter of type '(this: void) => number'. The 'this' types of each signature are incompatible. Type 'void' is not assignable to type 'Foo'. 。方法中this: this的值是否应隐式为封闭类型的值?是否有另一种方法可以实现相同的结果,而不会将所有样板噪声添加到我的类中?

plunker for discussed code

3 个答案:

答案 0 :(得分:0)

您可以将this参数明确地传递给回调,就像内置的javascript函数(如map)也支持显式this一样:

function consumer<T>(valueProducer: () => T, thisArg: any) {
    let value = valueProducer.apply(thisArg);
    console.log(value);
}

class Foo {
    private _value: number = 100;

    getValue(): number {
        return this._value;
    }

    constructor() {
        consumer(this.getValue, this);
    }
}

从我看来比箭头功能方法更丑陋,但至少你不能忘记传递它。

答案 1 :(得分:0)

这是解决方案#1。如果您将它与C#进行比较,那还不错。

class Foo {
    private _value: number = 100;

    getValue = () => this._value; // <- do this instead

    constructor() {
        // No problems with "this" now.
        consumer(this.getValue);
    }
}

getValue将被视为类成员,并将被分配给绑定到this的函数。因此,您不再需要捕获此错误,因为它不再是错误。这是由转译器为ES5目标生成的代码:

function Foo() {
    // Inserted by the TypeScript transpiler with ES5 target:
    var _this = this;
    this.getValue = function () { return _this._value; };
    ...
    // No problems with "this" now.
    consumer(this.getValue);
}

Check it out在线,位于TypeScript游乐场。

尽管如此,它不会解决在代码中找到此类位置的问题。只是会使重构更加容易。

如果您需要使方法正确反映在原型中,请使用解决方案2。没那么花哨,但是继承更好。

class Foo {
    private _value: number = 100;

    getValue(){ return this._value; }

    constructor() {
        this.getValue = this.getValue.bind(this);

        // No problems with "this" now.
        consumer(this.getValue);
    }
}

答案 2 :(得分:0)

您可以将EsLintplugin一起用于TypeScript。它有一条禁止未绑定方法的规则:

  211:19  error    Avoid referencing unbound methods which may cause unintentional scoping of `this`  @typescript-eslint/unbound-method

使用EsLint通常是一个好主意,因此这不是问题。请注意,此规则需要类型检查,因此您需要扩展插件中的所有规则集:

{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended"
  ]
}

您当然可以仅启用此规则,但是坚持建议的配置是更好的主意。