简体   繁体   English

如何在 typescript 中正确键入 class 变量

[英]How to properly type a variable that is a class in typescript

I have a class hierarchy that represents the records in a database, one class for each table.我有一个代表数据库中记录的 class 层次结构,每个表都有一个 class 。 I want to be able to properly type check the class objects (that represent the tables) as well as the instances of the class (that represent the individual rows in the database).我希望能够正确键入检查 class 对象(表示表)以及 class 的实例(表示数据库中的各个行)。 The only way I can get my code to compile is to disable type checking by casting the variables that hold the class to <any> .我可以编译我的代码的唯一方法是通过将保存 class 的变量转换为<any>来禁用类型检查。 Below is a contrived example that demonstrates the issue/questions I am having.下面是一个人为的示例,演示了我遇到的问题/问题。

My primary question is about types for variables holding classes.我的主要问题是关于持有类的变量的类型。 The answer to all three of these questions should, I think, have the same answer:我认为这三个问题的答案应该是相同的:

  • What type should I give to the values in the name dictionary (defined on line 3 of the code)?我应该为name字典中的值赋予什么类型(在代码的第 3 行定义)?
  • What type should I give to cls (defined on line 31)?我应该给cls什么类型(在第 31 行定义)?
  • Similarly, what type should cls be in the function maybeMakeInstance on line 49同样,第 49 行的 function maybeMakeInstance中的cls应该是什么类型

Bonus questions:奖金问题:

  • Is there a way to ensure that a static member (eg, names defined on line 3) is never defined in a subclass?有没有办法确保 static 成员(例如,在第 3 行定义的names )永远不会在子类中定义?
  • What is the proper way to declare static members that MUST be redefined in subclasses (eg, tablename and all defined on lines 6 and 7 and redefined on lines 61 and 62)?声明必须在子类中重新定义的tablename成员(例如,表名和all在第 6 行和第 7 行定义并在第 61 和 62 行重新定义)的正确方法是什么?
class A {
    // this should not be inheritable
    static names:{ [name: string]: { new(key:string):A} } = {};

    // next two need to be inherited in each subclass
    static tablename:string = "Aclass";
    static all: Record<string, A> = {};

    id: string;

    // create a new object and put into per class cache, `all`
    constructor(key:string = null) {
        this.id = key;
        if (key != null) {
            new.target.all[key] = this;
        }
    }

    // return instance matching `key`.  Will only search in exact class (not subclasses or superclasses)
    static find(key:string):A|null {
        if (key in this.all) {
            return this.all[key];
        }
        console.log(`${key} not in ${this.tablename}`);
        return null;
    }

    // pretty print info about instance
    show():void {
        // What is proper type for `this.constructor`?  <{ new(key:string):A}> fails as does doing nothing.
        const cls = <any>this.constructor;
        console.log(`${this.id} from ${cls.tablename}`);
    }

    // pretty print info about instance
    static showall():void {
        for (let x in this.all) {
            this.all[x].show();
        }
    }

    static init(name:string):void {
        this.names[name] = this;
    }

    static maybeMakeInstance(clsname:string, key:string):A {
        if ( !(clsname in A.names) ) throw new Error(`unknown classname: ${clsname}`);
        // what is proper type of `cls`?
        let cls:any = A.names[clsname];
        if (key in cls.all) {
            console.log(`Reusing ${key} in class ${clsname}/${cls.tablename}`);
            return cls.all[key];
        }
        return new cls(key);
    }
};
A.init('classA');

class B extends A {
    // is this proper way to override superclass static members?
    static tablename:string = "Bclass";
    static all: Record<string, B> = {};
}
B.init('classB');

// make sure above code is working.
function main() {
    let a = new A('first');
    A.showall();
    A.find('first').show();
    new A('second');
    new B('one');
    A.showall();
    B.showall();
    console.log(B.find('first'));
    console.log(B.find('second'));
    console.log(B.find('one'));
    console.log(A.find('one'));
    A.maybeMakeInstance('classA', 'third');
    A.maybeMakeInstance('classB', 'two');
    A.maybeMakeInstance('classB', 'two');
    console.log('------ A');
    A.showall();
    console.log('------ B');
    B.showall();
    A.maybeMakeInstance('classA', 'two');
    console.log('------ A');
    A.showall();
}
main();
////////////////
// running this file will result in the following output:
////////////////
// first from Aclass
// first from Aclass
// first from Aclass
// second from Aclass
// one from Bclass
// first not in Bclass
// null
// second not in Bclass
// null
// B { id: 'one' }
// one not in Aclass
// null
// Reusing two in class classB/Bclass
// ------ A
// first from Aclass
// second from Aclass
// third from Aclass
// ------ B
// one from Bclass
// two from Bclass
// ------ A
// first from Aclass
// second from Aclass
// third from Aclass
// two from Aclass
////////////////

The answer to both bonus questions is basically "you can't" which to me indicates that this isn't a great design.这两个额外问题的答案基本上都是“你不能”,这对我来说表明这不是一个伟大的设计。 So I basically rewrote your code rather than answering your typing questions, which I hope is helpful.所以我基本上重写了你的代码,而不是回答你的打字问题,我希望这会有所帮助。

Your classA is trying to do too much.你的classA试图做的太多了。 It represents an individual table object, stores all instances of its own type, and stores the names of all other types.它代表一个单独的表 object,存储它自己类型的所有实例,并存储所有其他类型的名称。 Let's break those three uses into their own classes.让我们将这三种用途分解为它们自己的类。

Each object instance is a TableObject .每个 object 实例都是一个TableObject Its only method it print itself.它打印自己的唯一方法。

class TableObject {
    id: string;
    table: Table;

    // TableObject stores a reference to the table that it is in
    constructor (table: Table, id: string) {
        this.id = id;
        this.table = table;
    }

    // pretty print info about instance
    show():void {
        console.log(`${this.id} from ${this.table.tablename}`);
    }
}

Each class name is a Table .每个 class 名称都是一个Table The table stores the object instances, can find or create objects, and can print all objects.该表存储object实例,可以查找或创建对象,可以打印所有对象。

class Table {
    tablename: string;
    all: Record<string, TableObject> = {};

    // construct a table from a name
    constructor( tablename: string ) {
        this.tablename = tablename;
    }

    // return TableObject instance matching `key`.
    find(key:string): TableObject | null {
        const obj = this.all[key];
        if ( obj ) return obj;
        console.log(`${key} not in ${this.tablename}`);
        return null;
    }

    // create a new TableObject instance and put into per class cache, `all`
    create(key:string): TableObject {
        const obj = new TableObject(this, key);
        this.all[key] = obj;
        return obj;
    }

    // pretty print info about all TableObject instances in this table
    showAll(): void {
        for (let x in this.all) {
            this.all[x].show();
        }
    }
}

The different tables are stored in a Database .不同的表存储在Database中。 The database can execute the maybeMakeInstance and showAll functions from your previous class A .数据库可以执行之前class A中的maybeMakeInstanceshowAll函数。

class Database {

    // store an object of Table classes keked by table name
    tables: Record<string, Table> = {};

    // can create an empty Database, or initialize with some Tables
    constructor( tables: Table[] = [] ) {
        tables.forEach(t => this.addTable(t));
    }

    // add an existing Table object to the Database
    addTable( table: Table ): void {
        this.tables[table.tablename] = table;
    }

    // create a new Table object and add it to the Database
    createTable( tablename: string ): Table {
        const table = new Table(tablename);
        this.addTable(table);
        return table;
    }

    // retrieve Table object by name
    getTable(tablename: string): Table | null {
        return this.tables[tablename] ?? null;
    }

    // find or create a TableObject based on the table name and object key
    maybeMakeInstance(tablename:string, key:string): TableObject {
        const table = this.getTable(tablename);
        if ( ! table ) throw new Error(`unknown table name: ${tablename}`);

        const existing = table.find(key);
        if (existing) {
            console.log(`Reusing ${key} in table ${tablename}`);
            return existing;
        }
        return table.create(key);
    }

    // show all result from all tables
    showAll(): void {
        Object.values(this.tables).forEach(t => t.showAll());
    }
}

Your code sample becomes something like this:您的代码示例如下所示:

function main() {
    const tableA = new Table('classA');
    tableA.create('first');
    tableA.showAll();
    tableA.find('first')?.show();
    tableA.create('second');
    const database = new Database([tableA]);
    const tableB = database.createTable('classB')
    tableB.create('one');
    tableA.showAll();
    tableB.showAll();
    console.log(tableB.find('first'));
    console.log(tableB.find('second'));
    console.log(tableB.find('one'));
    console.log(tableA.find('one'));
    database.maybeMakeInstance('classA', 'third');
    database.maybeMakeInstance('classB', 'two');
    database.maybeMakeInstance('classB', 'two');
    console.log('------ A');
    tableA.showAll();
    console.log('------ B');
    tableB.showAll();
    database.maybeMakeInstance('classA', 'two');
    console.log('------ A');
    database.showAll();
}
main();

It is now able to distinguish between different tables, with the final output printing:它现在能够区分不同的表,最终打印 output:

"first from classA" 
"second from classA" 
"third from classA" 
"two from classA" 
"one from classB" 
"two from classB" 

Typescript Playground Link Typescript 游乐场链接

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

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