使用Typescript Generics从AngularFire的已定义结构中创建一个接口

问题描述 投票:2回答:1

我试图定义我的数据结构来封装在一个类中处理数据的逻辑。然后我有拥有此信息的Field类。

Field<T>(private name:string, private size:number, private comment:string) {

}

get Name():string {
    return this.name;
}

get Size():number {
    return this.size;
}

创建一个Field引用:

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个数字。

我使用它来验证表单上的数据(使用Size来限制可输入的字符数)以及从外部API读取时。

如果我从外部API获取数据,我还可以使用“大小”字段来限制我复制到字段中的数据量,以便截断数据。

这允许我的数据接口层使用数据类型类,期望所有字段都是Field<T>,然后允许我使用我的库函数来控制数据大小,向表单添加数据验证等,而不必总是写验证HTML中的函数,因为我可以在Angular中使用循环并从数据结构中提取信息。

我现在的问题是如何获得一个通用接口来处理来自AngularFire中列表和对象的数据。

通常,当从AngularFire访问数据和结构时,我可以使用:

constructor(public afDb:AngularFireDatabase) {
    ...
    this.afDb.object<IUser>('/user/1').valueChanges();
    ...
}

这将获取数据并自动将其解析为IUser接口(上面未显示)。

我希望能够从我的User类生成一个IUser接口,该接口具有Field<T>中的数据结构。基本上我想从User类生成一个接口,如:

export interface IUser {
    FirstName?:string;
    LastName?:string;
    Age?:number;
}

然后可以在我访问AngularFire时使用此接口。另一个选项/问题是如何做相当于afDb.object<IUser>...并关闭<IUser>,但能够将AngularFire对象的结果解析为我的数据结构,即User类。所以解析会调用Field<T>.SetValue();或其他东西。

typescript generics angularfire
1个回答
1
投票

如果你想构建一个完全通用的接口来处理具有多个Field<T>s的所有不同对象,那么你应该考虑创建这样的东西:

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>(您可以根据需要扩展这些类型)。

这是你想要的?

- 更新更多有用的指导 -

在阅读您的评论并再次回顾您的答案之后,我认为我现在更了解您可能需要的内容。看看下一个例子,我认为它更适合你想做的事情。我评论了每个类和接口,以便更清楚地理解整个概念。如果您需要更多说明以及是否有帮助,请告诉我。


// 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);
}
© www.soinside.com 2019 - 2024. All rights reserved.