带有 Typescript 和 ES6 代理的 MongoModel Class
MongoModel Class with Typescript and ES6 Proxy
我想在打字稿中实现 MongoModel class。
这类似于 php 或 rails.
中的 ActiveRecord
class Model {
data: any;
}
let model = new Model();
我想完成的是
而不是model.data.name = 'John'
,我更愿意model.name = 'John'
。更复杂的是该字段应该是灵活的(如 mongo 所做的那样)。
model['anything'] = 'can be assigned'
最终会在数据 属性 中赋值。将与 model.data['anything'] = 'can be anything'
.
相同
我试过 defineProperty,但它不能处理所有 setters/getters。
我试过 Proxy 但不确定如何包装 Model class proxy.
嗯,我不知道 "MongoModel" 是什么,我也不知道 Base
、collectionName
和 docId
应该是什么。因此,如果以下内容不完全符合您的期望并且您无法适应它,您可能需要考虑编辑您的代码以构成 minimal reproducible example。但是让我们看看我们能在这里做什么。
首先,让我们将您的 Model
重命名为 InnerModel
,这将用于实现您想要的类型,但不会向 as-is 用户公开:
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}
你会很不开心。所以不要那样做。
无论如何,这里的目标是制作一些实际构造 Model<T>
.
实例的东西
请注意,编译器无法真正验证您从现在开始所做的操作是否是类型安全的,因此您需要使用 type assertions 或等效项来防止编译器发出错误。这意味着你必须小心,你只断言你自己可以验证为真的东西。
首先,我们将添加一个助手 type guard function 来帮助区分 属性 名称是否是对象的已知键...接下来我们将使用它来帮助编译器了解 属性 名称是否是对象的已知键。 =89=] 键位于 InnerModel
本身或嵌套的 data
属性:
function hasKey<T extends object>(obj: T, k: keyof any): k is keyof T {
return k in obj;
}
这是实现的主要部分...使用代理将 属性 gets/sets 路由到模型或数据,具体取决于找到密钥的位置
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()
处理程序 return any
和 value
是在 set()
处理程序中输入为 any
。这主要关闭了类型检查,因此我们需要手动检查正确性。并且编译器看不到我们 returning Model<T>
而不是 InnerModel<T>
,所以我们需要断言。
最后,我们将采用 makeModel()
函数并将其视为构造函数。在 JavaScript 中,您可以将任何函数用作 constructor,如果该函数 return 是一个值,则构造的对象将是该 return 值。编译器真的不喜欢我们这样做,所以需要双重断言:
const Model = makeModel as unknown as new <T>(data: T) => Model<T>;
但现在我们有了一些有用的东西:
const n = new Model({
a: "hey",
b: 1,
c: true,
d() {
console.log("D");
}
});
console.log(n.a); // hey
n.a = "you";
console.log(n.a); // you
console.log(n.data.a); // you
n.d(); // D
n.doSave(); // saving
好的,希望对您有所帮助。祝你好运!
编辑:如果你想让它成为 "flexible",你应该通过做 new Model<{[k: string]: any}>({})
之类的事情来使 T
"flexible",但是模型越灵活,少 strongly-typed 是的。是否要使用 indexable type 取决于您,但不会真正影响上面的实现(或者影响不大)
我想在打字稿中实现 MongoModel class。 这类似于 php 或 rails.
中的 ActiveRecordclass Model {
data: any;
}
let model = new Model();
我想完成的是
而不是model.data.name = 'John'
,我更愿意model.name = 'John'
。更复杂的是该字段应该是灵活的(如 mongo 所做的那样)。
model['anything'] = 'can be assigned'
最终会在数据 属性 中赋值。将与 model.data['anything'] = 'can be anything'
.
我试过 defineProperty,但它不能处理所有 setters/getters。 我试过 Proxy 但不确定如何包装 Model class proxy.
嗯,我不知道 "MongoModel" 是什么,我也不知道 Base
、collectionName
和 docId
应该是什么。因此,如果以下内容不完全符合您的期望并且您无法适应它,您可能需要考虑编辑您的代码以构成 minimal reproducible example。但是让我们看看我们能在这里做什么。
首先,让我们将您的 Model
重命名为 InnerModel
,这将用于实现您想要的类型,但不会向 as-is 用户公开:
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}
你会很不开心。所以不要那样做。
无论如何,这里的目标是制作一些实际构造 Model<T>
.
请注意,编译器无法真正验证您从现在开始所做的操作是否是类型安全的,因此您需要使用 type assertions 或等效项来防止编译器发出错误。这意味着你必须小心,你只断言你自己可以验证为真的东西。
首先,我们将添加一个助手 type guard function 来帮助区分 属性 名称是否是对象的已知键...接下来我们将使用它来帮助编译器了解 属性 名称是否是对象的已知键。 =89=] 键位于 InnerModel
本身或嵌套的 data
属性:
function hasKey<T extends object>(obj: T, k: keyof any): k is keyof T {
return k in obj;
}
这是实现的主要部分...使用代理将 属性 gets/sets 路由到模型或数据,具体取决于找到密钥的位置
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()
处理程序 return any
和 value
是在 set()
处理程序中输入为 any
。这主要关闭了类型检查,因此我们需要手动检查正确性。并且编译器看不到我们 returning Model<T>
而不是 InnerModel<T>
,所以我们需要断言。
最后,我们将采用 makeModel()
函数并将其视为构造函数。在 JavaScript 中,您可以将任何函数用作 constructor,如果该函数 return 是一个值,则构造的对象将是该 return 值。编译器真的不喜欢我们这样做,所以需要双重断言:
const Model = makeModel as unknown as new <T>(data: T) => Model<T>;
但现在我们有了一些有用的东西:
const n = new Model({
a: "hey",
b: 1,
c: true,
d() {
console.log("D");
}
});
console.log(n.a); // hey
n.a = "you";
console.log(n.a); // you
console.log(n.data.a); // you
n.d(); // D
n.doSave(); // saving
好的,希望对您有所帮助。祝你好运!
编辑:如果你想让它成为 "flexible",你应该通过做 new Model<{[k: string]: any}>({})
之类的事情来使 T
"flexible",但是模型越灵活,少 strongly-typed 是的。是否要使用 indexable type 取决于您,但不会真正影响上面的实现(或者影响不大)