打字稿在构造函数中使用“此”类型

时间:2020-08-06 09:12:20

标签: javascript typescript generics types this

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

我想做的一个最小例子:

// 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类型。为什么不允许这样做?有更好的方法吗?


不安全的替代方案

目前,我正在使用以下不安全替代方法。

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

2 个答案:

答案 0 :(得分:1)

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

不幸的是,关键字“ this ”不能用作类型,因此不能用于泛型,如其他答案所述。可以重写您的代码,而不必使用“ this ”,而只需使用当前类型BUT,这将不是“保证”,即表内的对象将是同一类型,这就是您将其描述为必要。

不幸的是,在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');

我知道我可能会稍微改变您的模型结构,但我不了解您的整体情况,并且我确定,如果您喜欢此解决方案,可以根据自己的喜好更改它,这只是一个原型。 / p>

我希望这是您所需要的。


我的答案的这一部分试图解释我的理论,为什么不能在构造函数中使用“ 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编译器也可能能够找出“此”类型。对于TypeScript团队来说,这无疑是一个不错的建议。

答案 1 :(得分:0)

Type<ContentType>注释仅用于类型,表示Table<this>无效,因为this总是 引用实例,而不是类/类型/接口等。意味着:将this作为table.instances.set(id, this);中的参数是有效的,但是Table<this>不是,您应该将其更改为Table<Model>