我想在打字稿中实现MongoModel类。 类似于php或rails中的ActiveRecord。
class Model {
data: any;
}
let model = new Model();
我想完成的是
我更喜欢model.data.name = 'John'
,而不是model.name = 'John'
。更复杂的是该字段应该是灵活的(就像mongo一样)。
model['anything'] = 'can be assigned'
将最终在data属性中分配。与model.data['anything'] = 'can be anything'
相同。
我尝试了defineProperty,但是它不能处理所有的setter / getter。 我已经尝试使用代理,但是不确定如何包装Model类代理。
答案 0 :(得分:1)
嗯,我不知道“ MongoModel”是什么,也不知道Base
,collectionName
和docId
应该是什么。因此,如果以下内容与您的期望不完全匹配并且您无法适应它,则可能需要考虑编辑代码以构成minimal reproducible example。但是,让我们看看我们可以在这里做什么。
首先,让我们将Model
重命名为InnerModel
,这将用于实现您想要的类型,但不会按原样向用户公开:
class InnerModel<T> {
constructor(public data: T) {}
doSave(): boolean {
console.log("saving");
return true; // implement me
}
}
请注意,我为它提供了一个构造器来保存类型T
的数据。现在的目标是制作类似Model<T>
和T
的类型:
InnerModel<T>
如果您有type Model<T> = T & InnerModel<T>;
实例,则可以直接访问Model<T>
的所有属性以及T
的所有属性。请注意,如果InnerModel<T>
与T
具有某些相同的属性名称,我将完全忽略。如果InnerModel
是T
,那真是太糟糕了,您将会度过一段糟糕的时光。所以不要那样做。
无论如何,这里的目标是制作一些实际上构成{data: number, doSave: boolean}
实例的东西。
请注意,编译器无法真正验证您从头开始执行的操作是否是类型安全的,因此您将需要使用type assertions或同等功能来防止编译器发出错误。这意味着您必须谨慎,只声明自己可以验证为真实的内容。
首先,我们将添加一个辅助程序type guard function,以帮助区分属性名称是否是对象的已知键...我们将在下一步中使用该帮助程序来帮助编译器了解属性键是否位于{{ 1}}本身或嵌套的Model<T>
属性上:
InnerModel
这是实现的主要部分...使用代理将属性获取/设置路由到模型或数据,具体取决于在哪里找到键
data
在某些地方这不是类型安全的... function hasKey<T extends object>(obj: T, k: keyof any): k is keyof T {
return k in obj;
}
和function makeModel<T>(data: T): Model<T> {
return new Proxy(new InnerModel(data), {
get(model: InnerModel<T>, prop: keyof Model<T>) {
return hasKey(model, prop) ? model[prop] : model.data[prop];
},
set(model: InnerModel<T>, prop: keyof Model<T>, value: any) {
return hasKey(model, prop)
? (model[prop] = value)
: (model.data[prop] = value);
}
}) as Model<T>;
}
处理程序返回get()
,而set()
的类型为{{1} } any
处理程序中。这通常会关闭类型检查,因此我们需要手动检查正确性。而且编译器看不到我们返回的是value
而不是any
,因此我们需要断言。
最后,我们将使用该set()
函数并将其视为构造函数。在JavaScript中,您可以将任何函数用作constructor,如果该函数返回一个值,则构造的对象将是该返回值。编译器确实不喜欢我们这样做,因此需要双重声明:
Model<T>
但是现在我们有了一些可行的方法:
InnerModel<T>
好的,希望能有所帮助。祝你好运!
编辑:如果您希望它“灵活”,则应通过进行类似makeModel()
的操作来使const Model = makeModel as unknown as new <T>(data: T) => Model<T>;
为“灵活”,但是模型越灵活,其类型越强。是否要使用indexable type取决于您,但实际上并不会影响上述实现(反正影响不大)