javascript - 如何在 TypeScript 中使类实例可构造?
问题描述
我正在尝试将此包转换为 TypeScript,而无需进行任何重大更改。我在 TypeScript 中有以下代码。
// DocumentCarrier.ts
/* export */ class DocumentCarrier {
internalObject: {};
model: Model;
save: (this: DocumentCarrier) => void;
constructor(model: Model, object: {}) {
this.internalObject = object;
this.model = model;
}
}
DocumentCarrier.prototype.save = function(this: DocumentCarrier): void {
console.log(`Saved document ${JSON.stringify(this.model)} to ${this.model.myName}`);
};
// Model.ts
// import {DocumentCarrier} from "./DocumentCarrier.ts";
/* export */class Model {
myName: string;
Document: typeof DocumentCarrier;
get: (id: number) => void;
constructor(name: string) {
this.myName = name;
const self: Model = this;
class Document extends DocumentCarrier {
static Model: Model;
constructor(object: {}) {
super(self, object);
}
}
Document.Model = self;
Object.keys(Object.getPrototypeOf(this)).forEach((key) => {
Document[key] = this[key].bind(this);
});
this.Document = Document;
return this.Document as any;
}
}
Model.prototype.get = function(id: number): void {
console.log(`Retrieving item with id = ${id}`);
}
// Usage
// index.ts
// import {Model} from "./Model.ts";
const User = new Model("User");
const user = new User({"id": 5, "name": "Bob"});
user.save(); // "Saved document {"id": 5, "name": "Bob"} to User"
console.log(User.Model.myName); // "User"
// console.log(User.myName); // "User" // This option would be even better, but isn't supported in the existing code
User.get(5); // "Retrieving item with id = 5"
在这Usage
部分(上面代码示例的最底部)中,我在 TypeScript 中遇到了多个错误。但是在 JavaScript 文件中运行该代码,效果很好。所以我知道它正在工作并且代码是准确的。
我认为我正在尝试做的最大问题是return this.Document as any
. TypeScript 将其解释为转换this.Document
为 Model 实例,而实际上并非如此。
我的问题是这个。在 TypeScript 中,我如何设置它可以运行new MyClassInstance()
并让它返回不同类的实例?这具有来自MyClassInstance
不同类的双向引用。简而言之,如何使以下代码正常工作?
任何解决方案都适用于该Usage
部分,并且不对该部分进行任何修改,这一点很重要。除了User.Model.myName
vsUser.myName
部分,它会被首选为User.myName
,但在现有版本中的功能为User.Model.myName
.
为了方便使用,我还创建了一个TypeScript Playground。
解决方案
我将把这个问题严格解释为“我怎样才能对现有代码进行类型化,以便编译器理解该Usage
部分中的代码?” 也就是说,答案不应该触及发出的 JavaScript,而应该只改变类型定义、注释和断言。
除了:更一般的问题“我应该如何实现一个实例本身就是类构造函数的类”是一个我不会尝试解决的问题,因为根据我的研究,这里的最佳答案是“不要尝试这样做”,因为它与 JS 中的原型继承模型配合不佳。相反,我强烈倾向于让不可构造的类实例拥有一个属性,该属性是新类的构造函数。像这样的游乐场代码。我预计,从长远来看,你会更快乐。
回到类型:这里的主要问题是 TypeScript 无法指定类构造函数返回的类型不是被定义的类。这是有意的(参见microsoft/TypeScript#11588或缺少的功能(参见microsoft/TypeScript#27594),但无论如何它不是语言的一部分。
我们在这里可以做的是使用声明合并。当您编写时,class Model {}
您同时引入了一个名为的类构造函数对象Model
和一个名为 的接口类型Model
。可以合并该接口,添加编译器尚不知道的方法和属性。在您的情况下,您可以这样做:
interface Model {
new(object: {}): DocumentCarrier;
Model: Model;
}
这让编译器知道Model
实例,除了在类中声明的属性/方法外,还有一个Model
类型为 的属性Model
,重要的是,还有一个构造函数签名。这足以让以下代码无错误地编译:
const User = new Model("User");
const user = new User({ "id": 5, "name": "Bob" });
user.save(); // "Saved document {"id": 5, "name": "Bob"} to User"
console.log(User.Model.myName); // "User"
User.get(5); // "Retrieving item with id = 5"
编译器确实认为User.myName
存在,它在运行时不存在,但这已经是现有代码的问题,所以我在这里不涉及。可以进一步更改类型,以便编译器知道User.Model.myName
存在和User.myName
不存在,但这变得非常复杂,因为它需要您将Model
的接口拆分为您仔细分配给正确值的多种类型。所以现在我忽略它。
我在这里要做的唯一其他更改是为 的实现提供不同的类型Model
,如下所示:
class Model {
myName: string;
Document: Model;
get!: (id: number) => void;
constructor(name: string) {
this.myName = name;
const self: Model = this;
class Document extends DocumentCarrier {
static Model: Model;
constructor(object: {}) {
super(self, object);
}
}
Document.Model = self;
(Object.keys(Object.getPrototypeOf(this)) as
Array<keyof typeof DocumentCarrier>).forEach((key) => {
Document[key] = this[key].bind(this);
});
this.Document = Document as Model;
return this.Document;
}
}
编译器在上面唯一无法验证的是Document
类是有效的Model
,所以我们使用断言Document as Model
。除此之外,我只是提出了一些断言(肯定get
是赋值,Object.keys()
并将返回构造函数的键数组DocumentCarrier
),这样您就不需要关闭--strict
编译器标志。
好的,希望有帮助。祝你好运!
推荐阅读
- arrays - 当从数据集中删除行时,如何创建工作表公式来更新调整后的分数和排名?
- python - 如何使用 precommit 和 hook 探矿者修复 CalledProcessError 问题?
- python - 如何使用 python SDK 筛选 Azure 表中的特定行
- flutter - 可以在 main.dart Flutter 中调用提供者函数吗
- javascript - ES6 类:将“this”绑定到嵌套函数
- javascript - 如何使用 React Router 将数据传递给不在 URL 中的另一个组件
- python - OpenCV 录制的网络摄像头视频比现实生活中的 Python 更快
- azure - 是否可以将 Azure SignalR 服务与本地 SignalR 结合使用?
- javascript - 调用vue路由器时超出最大调用堆栈大小
- c++ - OpenGL奇怪的鼠标旋转结果