【问题标题】:Typescript Using 'this' type in constructorsTypescript 在构造函数中使用“this”类型
【发布时间】:2020-11-26 12:40:07
【问题描述】:

用例:我想为特定类(Model 类)实例的查找表(Table 类)建模一个通用系统。

我想做的一个最小的例子:

// Generic part
abstract class Table<T extends Model> {
    instances: Map<number, T> = new Map();
}
abstract class Model {
    constructor(
        public readonly id: number,
        public table: Table<this>  // Error
    ) { 
        table.instances.set(id, this);
    }
}

// Example: a table of Person objects
class Person extends Model {
    constructor(
        id: number,
        table: Table<this>,  // Error
        public name: string
    ) {
        super(id, table);
    }
}
class PersonTable extends Table<Person> {}

const personTable = new PersonTable();
const person = new Person(0, personTable, 'John Doe');

// Note: the idea of using `this` as generic type argument is to guarantee
// that other models cannot be added to a table of persons, e.g. this would fail:
//     class SomeModel extends Model { prop = 0; }
//     const someModel = new SomeModel(1, person.table);
//                                        ^^^^^^^^^^^^

不幸的是,TypeScript 抱怨构造函数中的 this 类型。为什么不允许这样做?有没有更好的方法来做到这一点?


不安全的替代方案

目前我正在使用以下 unsafe 替代方案。

// Generic part
abstract class Table<T extends Model> {
    instances: Map<number, T> = new Map();
}
abstract class Model {
    public table: Table<this>;
    constructor(
        public readonly id: number,
        table: Table<Model>
    ) { 
        table.instances.set(id, this);
        this.table = table as Table<this>;
    }
}

// Example: a table of Person objects
class Person extends Model {
    constructor(
        id: number,
        table: Table<Person>,
        public name: string
    ) {
        super(id, table);
    }
}
class PersonTable extends Table<Person> {}

回复评论

回答 Liam 的评论:this 类型的一个非常简单的安全示例。

class A {
    someInstances: this[] = [];
}
class B extends A {
    someProp = 0;
}
const a = new A();
const b = new B();
a.someInstances.push(b);
// This isn't allowed: b.someInstances.push(a);

【问题讨论】:

  • Model 是一个类型,this 是一个类型的instance。所以它是无效的,你想要Table&lt;Person&gt; 而不是Table&lt;this&gt;
  • 在该链接中使用的this 中,它只是作为类型(: this)返回,而不是泛型类型(Type&lt;this&gt;)。所以我猜泛型不支持这种类型的类型。
  • @Liam 在泛型中支持,只是在构造函数中不支持。有关在合法场所使用的示例,请参阅我的“不安全替代方案”。
  • @Liam 对于使用 this 类型的 shrug 示例,请参阅我的编辑。

标签: javascript typescript generics types this


【解决方案1】:

我想我能够为您的问题提出解决方案。不幸的是由于语言限制,它可能不是很优雅,但也不错。

不幸的是,关键字“this”不能用作类型,因此不能在泛型中使用,正如其他答案所述。在您的情况下,您可以重写您的代码,而不是“this”,只需使用当前类型,但是,这不会“保证”您的 Table 中的对象将是相同的类型,这就是您根据需要进行了描述。

不幸的是,在 JavaScript/TypeScript 中,你不能保证任何泛型集合中的对象都是“通过类型化”的相同类型,因为 TypeScript 不提供协变、逆变和不变等工具。您必须使用代码和检查来确保它。这是一个已知问题,例如在 Promise 中,您可以返回不应该返回的类型。 (至少这是我刚刚知道和发现的,不是 100% 肯定)

要创建一个所有成员都属于同一类型的不变表,我们必须检查每个输入的元素。我提出了一种可能的模型,其中每个表都接受一个用户定义的函数,该函数检查可以允许哪些类型以及禁止哪些类型:

interface TypeGuard<T>
{
    (inputObject: T): boolean;
}

// Generic part
class SingleTypeTable<T>
{
    private typeGuard: TypeGuard<T>;
    constructor(typeGuard: TypeGuard<T>)
    {
        this.typeGuard = typeGuard;
    }

    Add(item: T)
    {
        //Check the type
        if (!this.typeGuard(item))
            throw new Error("I do not like this type");

        //...
    }
}

人员守卫的工作方式如下:

const personGuard: TypeGuard<Person> = function (person: Person): boolean
{
    return person instanceof Person;
}

personGuard(new Person(...)); //true
personGuard("string" as any as Person); //false

现在您可以按如下方式创建模型和人物:

// Some abstract model
abstract class Model<T>
{
    constructor(
        public readonly id: number,
        public table: SingleTypeTable<T>  //Table of models
    )
    {
        
    }
}

// Example: a table of Person objects
class Person extends Model<Person>
{
    constructor(
        id: number,
        table: SingleTypeTable<Person>,
        public name: string
    )
    {
        super(id, table);
    }
}

//Usage 
const personTable = new Table<Person>(personGuard);
const person = new Person(0,  personTable , 'John Doe');

我知道我可能稍微改变了你的模型结构,但我不知道你的整体情况,我相信如果你喜欢这个解决方案,你可以根据自己的喜好改变它,这只是一个原型。

我希望这是你需要的。


我的这部分回答试图解释我为什么不能在构造函数中使用“this”关键字作为参数类型的理论。

首先,你不能在函数中使用“this”作为类型。你不能这样做:

function foo(a: this) {} //What is "this"? -> Error

在我进一步解释之前,我们需要回到纯 JavaScript。创建对象的方法之一是“实例化函数”,如下所示:

function Animal(name) { this.name = name; }
var dog = new Animal("doggo");

这几乎就是 TypeScript 类被编译成的样子。你看,这个 Animal 对象是一个函数,也是 Animal 对象的构造函数。

那么,为什么不能在 TypeScript 构造函数中使用“this”关键字呢?上面看,构造函数编译成函数,构造函数参数只是函数的一些参数,不能有“this”的类型。

然而,TypeScript 编译器可能能够找出“this”类型,即使是构造函数参数。这对于 TypeScript 团队来说无疑是一个很好的建议。

【讨论】:

  • “不幸的是,关键字“this”不能用作类型,因此不能在泛型中使用,正如其他答案所述“它可以在泛型中使用,我已经在仿制药。请参阅我对 Liam 评论的回答。
  • 虽然您确实提供了(草图)替代解决方案,但在某些情况下可能有用。我仍然希望这些类型检查可以在编译时而不是运行时完成。您也没有回答为什么 'this' 不能以这种方式在构造函数中用作类型的问题,尽管它可以完美地在构造函数之外使用,所以我将首先等待更好的答案。跨度>
  • 感谢指正。编译时解决方案将是最好的,我同意。
  • 关于“为什么'this'不能用作构造函数中的类型”的问题:您不能将“this”用作常规函数中的参数类型,我认为因为构造函数是也是一个常规函数(编译后),你也不能在那里使用它。 JavaScript 类基本上是当您在函数上使用“new”关键字时,构造函数参数是该函数的参数(非常粗略的解释)。
  • 我在答案中进一步解释了我的理论,希望我没有完全错,但似乎合乎逻辑。
【解决方案2】:

Type&lt;ContentType&gt; 注释仅用于类型,意味着Table&lt;this&gt; 无效,因为this总是指的是实例而不是类/类型/接口等。 : 在table.instances.set(id, this); 中传递this 作为参数是有效的,但Table&lt;this&gt; 不是,你应该把它改成Table&lt;Model&gt;。

【讨论】:

  • "this 总是指的是一个实例,而不是一个类/类型/接口"。这是不正确的。 polymorphic this types呢?
  • @RobbyCornelissen 好吧,您仍然需要创建该类的一个实例来执行这些函数,因为这些是非静态函数。即使上下文是通过继承给出的,它们仍然引用实例
  • 问题是:既然大家都同意this可以在TypeScript中用作类型,为什么不能在构造函数中用作类型参数?
  • 所以你应该添加一个答案然后@RobbyCornelissen?
  • @Liam 如果我有答案,我会提供。只是指出它不仅仅是说this 不能用作 TypeScript 中的类型,因为它显然可以。
猜你喜欢
  • 1970-01-01
  • 2019-09-19
  • 2022-06-25
  • 1970-01-01
  • 2016-08-21
  • 2019-05-04
  • 1970-01-01
  • 2018-09-21
  • 2021-02-07
相关资源
最近更新 更多