用例:我想为特定类(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);
答案 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>
。