【问题标题】:How to properly type a variable that is a class in typescript如何正确键入作为打字稿中的类的变量
【发布时间】:2021-05-04 12:37:13
【问题描述】:

我有一个代表数据库中记录的类层次结构,每个表一个类。我希望能够正确键入检查类对象(表示表)以及类的实例(表示数据库中的各个行)。编译代码的唯一方法是通过将保存类的变量强制转换为<any> 来禁用类型检查。下面是一个人为的例子,展示了我遇到的问题。

我的主要问题是关于持有类的变量的类型。我认为这三个问题的答案应该是相同的:

  • 我应该为name 字典(在代码的第 3 行定义)中的值赋予什么类型?
  • 我应该给cls(定义在第31行)什么类型?
  • 同样,第 49 行的函数 maybeMakeInstance 中的 cls 应该是什么类型

额外问题:

  • 有没有办法确保静态成员(例如,在第 3 行定义的 names)永远不会在子类中定义?
  • 声明必须在子类中重新定义的静态成员(例如,tablenameall 在第 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
////////////////

【问题讨论】:

  • 您在这里尝试做的一些事情,比如要求在子类中覆盖静态成员,有点违背 OOP 设计。您可能需要考虑重组它以使用组合而不是继承。
  • 忽略奖励问题(答案基本上是“否”,因为 TS 目前不支持abstract static 成员,并且可能永远不会支持final 成员),你也许可以逃脱只是typeof A。这里的问题是子类构造函数没有形成正确的类型层次结构。所以class B extends A 不保证typeof B 扩展typeof A。因此this.constructor 的类型太宽了,Function。如果 typeof A 适用于您的用例,那就太好了。如果没有,请说明失败的地方。
  • 请参阅this question 并让我知道它的答案是否足以解决这里发生的事情;如果没有,请让我知道,具体来说,仍然需要回答什么。祝你好运!

标签: typescript class types


【解决方案1】:

这两个额外问题的答案基本上都是“你不能”,这对我来说表明这不是一个伟大的设计。所以我基本上重写了你的代码,而不是回答你的打字问题,希望对你有所帮助。

您的classA 试图做的太多了。它代表一个单独的表对象,存储它自己类型的所有实例,并存储所有其他类型的名称。让我们将这三种用途分解为各自的类。

每个对象实例都是一个TableObject。它唯一的打印方法。

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}`);
    }
}

每个类名都是一个Table。该表存储对象实例,可以查找或创建对象,并且可以打印所有对象。

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();
        }
    }
}

不同的表存储在Database 中。数据库可以执行您之前的@​​987654330@ 中的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());
    }
}

您的代码示例变成这样:

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

现在可以区分不同的表,并打印最终输出:

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

Typescript Playground Link

【讨论】:

    猜你喜欢
    • 2018-09-18
    • 2014-10-13
    • 2021-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-18
    • 2020-06-27
    • 1970-01-01
    相关资源
    最近更新 更多