使用 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>;
}

这样你就可以将 IWhateverafDb.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);
}