使用 Typescript 泛型从 AngularFire 的已定义结构创建接口
Use Typescript Generics to make an interface from a defined structure for AngularFire
我正在尝试定义我的数据结构以将处理数据的逻辑封装在一个 class 中。然后我有包含此信息的字段 class。
Field<T>(private name:string, private size:number, private comment:string) {
}
get Name():string {
return this.name;
}
get Size():number {
return this.size;
}
创建字段引用:
class User {
public FirstName:Field<string> = new Field<string>('FirstName', 20);
public LastName:Field<string> = new Field<string>('LastName', 32);
public Age:Field<number> = new Field<number>('Age', 3);
}
上面的意思是我有一个数据字段'FirstName',它是一个字符串,最多 20 个字符。 'LastName'是一个32位的字符串,Age可以是3位数字。
然后我用它来验证我在表单上的数据(例如使用大小来限制可以输入的字符数)以及从外部读取数据时 API。
如果我从外部获取数据API,我还可以使用字段'Size'来限制我复制到字段中的数据量,从而截断数据。
这允许我的数据接口层使用数据类型 class,期望所有字段都是 Field<T>
,然后允许我使用我的库函数来控制数据大小,添加对表单等进行数据验证,而不必总是在 HTML 中编写验证函数,因为我可以在 Angular 中使用循环并从数据结构中提取信息。
我现在的问题是如何获得一个通用接口来处理来自 AngularFire 中的列表和对象的数据。
通常,当从AngularFire访问数据和结构时,我可以使用:
constructor(public afDb:AngularFireDatabase) {
...
this.afDb.object<IUser>('/user/1').valueChanges();
...
}
这将获取数据并自动将其解析到 IUser 界面(上面未显示)。
我希望能够从我的用户 class 生成一个 IUser 接口,它具有 Field<T>
中的数据结构。基本上我想从用户 class 生成一个界面,例如:
export interface IUser {
FirstName?:string;
LastName?:string;
Age?:number;
}
然后这个接口就可以用来访问AngularFire了。另一个 option/question 是如何执行 afDb.object<IUser>...
的等效操作并关闭 <IUser>
,但能够将 AngularFire 对象的结果解析到我的数据中结构是用户 class。所以解析会调用 Field<T>.SetValue();
或其他东西。
如果你想构建一个完全通用的接口来处理具有多个 Field<T>
的所有不同对象,那么你可能应该考虑创建这样的东西:
class Field<T> {
constructor(private name: string, private size: number) {
}
}
interface IField {
[index: string]: Field<string | number>;
}
interface IWhatever {
fields: ArrayLike<IField>;
}
这样你就可以将 IWhatever
与 afDb.object<IWhatever>()
一起使用并取回一个 IWhatever
,其中包含 Array
个类型为 IField
的对象,它是一个非常通用的接口,可以包含任意数量的命名属性,每个属性都具有具体类型 Field<string>
或 Field<number>
的值(您也可以根据需要扩展这些类型)。
这是您要找的吗?
--更新更有帮助的指导--
阅读您的评论并再次查看您的回答后,我想我现在更了解您可能需要什么。看看下一个例子,我认为它更适合你想做的事情。我对每个 class 和界面进行了注释,以便更清楚地理解整个概念。如果您需要更多说明以及是否有帮助,请告诉我。
// This is a implementation class of our concept of "Field" objects.
// The T is a generic type, which means that it can be any type every time
// you instantiate a new object of this class.
// the type will be used to define the type of the `value` variable
// that will hold the actual internal value of the "Field".
//
// So when you create a new Field<string>(....) you will always know
// that the `value` property will be of type `string` and not anything else.
class Field<T> {
public title: string;
public maxLength: number;
public value: T;
constructor(title: string, maxLength: number) {
this.title = title;
this.maxLength = maxLength;
}
}
// this is an interface, that defines an object with any number
// of properties of type Field<string> or Field<number>
interface IObject {
// can have any number of Field typed properties
[index: string]: Field<string | number>;
}
// this is a more specific version of the above interface, that
// actually defines exactly which fields and their types it
// should have and which ones should be required or optional
interface IUser {
// required fields
firstName: Field<string>;
lastName: Field<string>;
age: Field<number>;
// lets define an optional one
favoriteColor?: Field<string>;
}
// Suppose that we get a data structure from somewhere that has no type
// and that we want to encapsulate it inside one of our interfaces
// in order to make it easier to work with it
//
// So lets create a literal object with no specific type (a data structure):
let data = {
firstName: new Field<string>('First Name', 20),
lastName: new Field<string>('Last Name', 32),
age: new Field<number>('Age', 3),
}
// we can then assign this object or data structure to a
// variable with the very generic IObject as type
let anObjectOfUnknownType: IObject = data;
// no problem, no complaints from typescript, the data is compatible
// with our IObject interface!
// We can also assign it to a variable
// of type IUser, which is more specific
// and narrows down our data structure to the definition that
// we the IUser interface expects it to be. For this to work
// though we must make sure that the structure must satisfy
// and be compatible with the IUser interface.
let userDataStructure: IUser = data;
// and yes, it works, because the data is compatible with IUser
// We now have encapsulated the "unknown" data structure in a
// useful typed variable from which we can access its properties
// and enjoy type checking and intellisense
console.log("user's full name is: " + userDataStructure.firstName.value + " " + userDataStructure.lastName.value);
console.log("user's age is: " + userDataStructure.age);
if (typeof userDataStructure.favoriteColor !== "undefined") {
console.log("user's favorite color is: " + userDataStructure.favoriteColor.value);
}
我正在尝试定义我的数据结构以将处理数据的逻辑封装在一个 class 中。然后我有包含此信息的字段 class。
Field<T>(private name:string, private size:number, private comment:string) {
}
get Name():string {
return this.name;
}
get Size():number {
return this.size;
}
创建字段引用:
class User {
public FirstName:Field<string> = new Field<string>('FirstName', 20);
public LastName:Field<string> = new Field<string>('LastName', 32);
public Age:Field<number> = new Field<number>('Age', 3);
}
上面的意思是我有一个数据字段'FirstName',它是一个字符串,最多 20 个字符。 'LastName'是一个32位的字符串,Age可以是3位数字。
然后我用它来验证我在表单上的数据(例如使用大小来限制可以输入的字符数)以及从外部读取数据时 API。
如果我从外部获取数据API,我还可以使用字段'Size'来限制我复制到字段中的数据量,从而截断数据。
这允许我的数据接口层使用数据类型 class,期望所有字段都是 Field<T>
,然后允许我使用我的库函数来控制数据大小,添加对表单等进行数据验证,而不必总是在 HTML 中编写验证函数,因为我可以在 Angular 中使用循环并从数据结构中提取信息。
我现在的问题是如何获得一个通用接口来处理来自 AngularFire 中的列表和对象的数据。
通常,当从AngularFire访问数据和结构时,我可以使用:
constructor(public afDb:AngularFireDatabase) {
...
this.afDb.object<IUser>('/user/1').valueChanges();
...
}
这将获取数据并自动将其解析到 IUser 界面(上面未显示)。
我希望能够从我的用户 class 生成一个 IUser 接口,它具有 Field<T>
中的数据结构。基本上我想从用户 class 生成一个界面,例如:
export interface IUser {
FirstName?:string;
LastName?:string;
Age?:number;
}
然后这个接口就可以用来访问AngularFire了。另一个 option/question 是如何执行 afDb.object<IUser>...
的等效操作并关闭 <IUser>
,但能够将 AngularFire 对象的结果解析到我的数据中结构是用户 class。所以解析会调用 Field<T>.SetValue();
或其他东西。
如果你想构建一个完全通用的接口来处理具有多个 Field<T>
的所有不同对象,那么你可能应该考虑创建这样的东西:
class Field<T> {
constructor(private name: string, private size: number) {
}
}
interface IField {
[index: string]: Field<string | number>;
}
interface IWhatever {
fields: ArrayLike<IField>;
}
这样你就可以将 IWhatever
与 afDb.object<IWhatever>()
一起使用并取回一个 IWhatever
,其中包含 Array
个类型为 IField
的对象,它是一个非常通用的接口,可以包含任意数量的命名属性,每个属性都具有具体类型 Field<string>
或 Field<number>
的值(您也可以根据需要扩展这些类型)。
这是您要找的吗?
--更新更有帮助的指导--
阅读您的评论并再次查看您的回答后,我想我现在更了解您可能需要什么。看看下一个例子,我认为它更适合你想做的事情。我对每个 class 和界面进行了注释,以便更清楚地理解整个概念。如果您需要更多说明以及是否有帮助,请告诉我。
// This is a implementation class of our concept of "Field" objects.
// The T is a generic type, which means that it can be any type every time
// you instantiate a new object of this class.
// the type will be used to define the type of the `value` variable
// that will hold the actual internal value of the "Field".
//
// So when you create a new Field<string>(....) you will always know
// that the `value` property will be of type `string` and not anything else.
class Field<T> {
public title: string;
public maxLength: number;
public value: T;
constructor(title: string, maxLength: number) {
this.title = title;
this.maxLength = maxLength;
}
}
// this is an interface, that defines an object with any number
// of properties of type Field<string> or Field<number>
interface IObject {
// can have any number of Field typed properties
[index: string]: Field<string | number>;
}
// this is a more specific version of the above interface, that
// actually defines exactly which fields and their types it
// should have and which ones should be required or optional
interface IUser {
// required fields
firstName: Field<string>;
lastName: Field<string>;
age: Field<number>;
// lets define an optional one
favoriteColor?: Field<string>;
}
// Suppose that we get a data structure from somewhere that has no type
// and that we want to encapsulate it inside one of our interfaces
// in order to make it easier to work with it
//
// So lets create a literal object with no specific type (a data structure):
let data = {
firstName: new Field<string>('First Name', 20),
lastName: new Field<string>('Last Name', 32),
age: new Field<number>('Age', 3),
}
// we can then assign this object or data structure to a
// variable with the very generic IObject as type
let anObjectOfUnknownType: IObject = data;
// no problem, no complaints from typescript, the data is compatible
// with our IObject interface!
// We can also assign it to a variable
// of type IUser, which is more specific
// and narrows down our data structure to the definition that
// we the IUser interface expects it to be. For this to work
// though we must make sure that the structure must satisfy
// and be compatible with the IUser interface.
let userDataStructure: IUser = data;
// and yes, it works, because the data is compatible with IUser
// We now have encapsulated the "unknown" data structure in a
// useful typed variable from which we can access its properties
// and enjoy type checking and intellisense
console.log("user's full name is: " + userDataStructure.firstName.value + " " + userDataStructure.lastName.value);
console.log("user's age is: " + userDataStructure.age);
if (typeof userDataStructure.favoriteColor !== "undefined") {
console.log("user's favorite color is: " + userDataStructure.favoriteColor.value);
}