Машинопись с использованием этого типа в конструкторах - PullRequest
4 голосов
/ 06 августа 2020

Вариант использования: я хочу смоделировать общую c систему для таблиц поиска (Table класс) экземпляров определенного c класса (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> {}

Ответить на комментарий

Чтобы ответить на комментарий Лиама: очень простой безопасный пример типа 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 ]

1 голос
/ 06 августа 2020

Думаю, я смог предложить решение вашей проблемы. К сожалению, из-за языковых ограничений он может быть не очень элегантным, но и неплохим.

К сожалению, ключевое слово « this » нельзя использовать в качестве тип, поэтому его нельзя использовать в дженериках, как указано в других ответах. В вашем случае вы можете переписать свой код, и вместо « this » просто используйте текущий тип, НО, это не будет «гарантией» того, что объекты внутри вашей таблицы будут одного типа, что вы описали как необходимые.

К сожалению, в JavaScript / TypeScript вы не можете гарантировать, что объекты в любом общем c коллекции относятся к тому же типу «по типам», потому что TypeScript не предоставляет таких инструментов, как ковариация, контравариантность и инвариантность. Вы должны обеспечить это с помощью кода и проверок. Это известная проблема, например, в обещаниях, где вы можете возвращать типы, которые вам не следует. (По крайней мере, это то, что я знаю и нашел только сейчас, не уверен на 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.

Итак, почему вы не можете использовать ключевое слово this в конструкторе TypeScript? Если вы посмотрите выше, конструктор компилируется в функцию, а параметры конструктора - это всего лишь некоторые параметры функции, и они не могут иметь тип this.

Однако компилятор TypeScript может для определения типа «this» используется даже параметр конструктора. Это, безусловно, хорошее предложение для команды TypeScript.

0 голосов
/ 06 августа 2020

Аннотации Type<ContentType> используются только для типов, это означает, что Table<this> недействительно, потому что this - это всегда относится к экземпляру, а не к классу / типу / интерфейсу и т. Д. c . Значит: можно передать this в качестве аргумента в table.instances.set(id, this);, но Table<this> нет, вы должны изменить это на Table<Model>.

...