[英]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:
我认为这三个问题的答案应该是相同的:
name
dictionary (defined on line 3 of the code)?name
字典中的值赋予什么类型(在代码的第 3 行定义)?cls
(defined on line 31)?cls
什么类型(在第 31 行定义)?cls
be in the function maybeMakeInstance
on line 49maybeMakeInstance
中的cls
应该是什么类型Bonus questions:奖金问题:
names
defined on line 3) is never defined in a subclass?names
)永远不会在子类中定义?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
中的maybeMakeInstance
和showAll
函数。
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"
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.