简体   繁体   English

Typescript 在构造函数中使用“this”类型

[英]Typescript Using 'this' type in constructors

Use case: I want to model a generic system for look-up tables (a Table class) of instances of a specific class (a Model class).用例:我想要 model 一个通用系统,用于查找特定 class(一个Model类)实例的实例(一个Table类)。

A minimal example of what I would like to do:我想做的一个最小的例子:

// 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);
//                                        ^^^^^^^^^^^^

Unfortunately, TypeScript complains about the this type in the constructor.不幸的是,TypeScript 在构造函数中抱怨this类型。 Why isn't this allowed?为什么不允许这样做? Is there a better way to do this?有一个更好的方法吗?


Unsafe alternative不安全的替代品

For now I'm using the following unsafe alternative.现在我正在使用以下不安全的替代方案。

// 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> {}

Answer to comment回复评论

To answer a comment of Liam: a very simple safe example of the this type.回答 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);

I think I was able to propose a solution to your problem.我想我能够为您的问题提出解决方案。 Unfortunately due to the language restrictions, it may not be very elegant, but it is not a bad one either.不幸的是,由于语言限制,它可能不是很优雅,但也不错。

Unfortunately, the keyword " this " can not be used as a type so it can not be used in generics, as other answers stated.不幸的是,正如其他答案所述,关键字“ this ”不能用作类型,因此不能在 generics 中使用。 In your case, you can rewrite your code, and instead of " this ", just use the current type, BUT, this will not be the "guarantee" that objects inside your Table will be of the same type, which is what you described as necessary.在你的情况下,你可以重写你的代码,而不是“ this ”,只是使用当前类型,但是,这不会是你的 Table 中的对象将是相同类型的“保证”,这就是你所描述的有必要的。

Unfortunately, in JavaScript/TypeScript, you can not guarantee that objects in any generic collection are of the same type "by typings", because TypeScript does not provide tools such as covariance, contravariance, and invariance.不幸的是,在 JavaScript/TypeScript 中,你不能保证任何泛型集合中的对象都是“通过类型化”的相同类型,因为 TypeScript 不提供协变、逆变和不变等工具。 You have to ensure it using code and checks.您必须使用代码和检查来确保它。 This is a known issue for example in promises, where you can return types that you should not.这是一个已知问题,例如在 Promise 中,您可以返回不应该返回的类型。 (At leas this is what I know and found just now, not 100 % sure) (至少这是我现在所知道和发现的,不是 100% 确定的)

To create an invariant table, where all members are of the same type, we have to check every inputted element.要创建一个所有成员都属于同一类型的不变表,我们必须检查每个输入的元素。 I proposed one possible model, where every table accepts a user-defined function that checks what types can be let it and what types are forbidden:我提出了一种可能的 model,其中每个表都接受一个用户定义的 function,它检查哪些类型可以被允许,哪些类型被禁止:

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");

        //...
    }
}

The person guard works as follows:人员守卫的工作方式如下:

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

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

Now you can create your models and persons as follows:现在您可以按如下方式创建模型和人物:

// 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');

I understand that I might have changed your model structure a little bit but I do not know your overall picture and I am sure that if you like this solution, you can change it to your likings, this is just a prototype.我知道我可能稍微改变了你的 model 结构,但我不知道你的整体情况,我相信如果你喜欢这个解决方案,你可以根据自己的喜好进行更改,这只是一个原型。

I hope this is what you need.我希望这是你需要的。


This part of my answer tries to explain my theory of why you can not use "this" keyword in a constructor as a parameter type.我的这部分答案试图解释我为什么不能在构造函数中使用“this”关键字作为参数类型的理论。

First of all, you cant use "this" as a type in a function.首先,您不能在 function 中使用“this”作为类型。 You can not do this:你不能做这个:

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

Before I explain further, we need to take a trip back to plain JavaScript.在我进一步解释之前,我们需要回到简单的 JavaScript。 One of the ways to create an object is to "instantiate a function", like this:创建 object 的方法之一是“实例化函数”,如下所示:

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

This is almost exactly what TypeScript classes are compiled to.这几乎正是 TypeScript 类编译成的内容。 You see, this Animal object is a function, but also a constructor for the Animal object.你看,这个 Animal object 是一个 function,但也是 Animal object 的构造函数。

So, why you can not use "this" keyword in the TypeScript constructor?那么,为什么不能在 TypeScript 构造函数中使用“this”关键字呢? If you look above, a constructor is compiled into a function, and constructor parameters are just some parameters of a function, and these can not have the type of "this".如果你看上面,构造函数被编译成function,构造函数参数只是function的一些参数,这些不能有“this”的类型。

However, the TypeScript compiler might be able to figure out the "this" type even is the constructor parameter.但是,即使是构造函数参数,TypeScript 编译器也可能能够找出“this”类型。 This is certainly a good suggestion for a feature for the TypeScript team.对于 TypeScript 团队来说,这无疑是一个很好的建议。

The Type<ContentType> annotations is used for types only, means Table<this> is not valid, because this is always refering to an instance and not a class/type/interface etc. Means: it is valid to pass this as an argument in table.instances.set(id, this); Type<ContentType>注释仅用于类型,意味着Table<this>无效,因为this始终引用实例而不是类/类型/接口等。意味着:将this作为参数传递是有效的在table.instances.set(id, this); , but Table<this> is not, you should change this to Table<Model> . ,但Table<this>不是,您应该将其更改为Table<Model>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM