Используйте Typescript Generics для создания интерфейса из определенной структуры для AngularFire - PullRequest
2 голосов
/ 01 апреля 2019

Я пытаюсь определить свою структуру данных, чтобы инкапсулировать логику для работы с данными в одном классе. Затем у меня есть класс Field, в котором есть эта информация.

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, я также могу использовать поле «Размер», чтобы ограничить объем данных, которые я копирую в поле, чтобы обрезать данные.

Это позволяет моему слою интерфейса данных работать с классом типа данных, ожидая, что все поля будут Field<T>, а затем позволяет мне использовать функции библиотеки для управления размером данных, добавления проверки данных в формы и т. Д., Без всегда нужно писать проверяющие функции в HTML, так как я могу использовать циклы в Angular и извлекать информацию из структуры данных.

Мой вопрос теперь заключается в том, как получить общий интерфейс для работы с данными, поступающими из списков и объектов в AngularFire.

Обычно при доступе к данным и структуре из AngularFire я могу использовать:

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

Это позволит получить данные и автоматически проанализировать их в интерфейсе IUser (не показан выше).

Я хочу иметь возможность генерировать интерфейс IUser из моего класса User, который имеет структуры данных в Field<T>. По сути, я хочу создать интерфейс из класса User что-то вроде:

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

Тогда этот интерфейс можно было бы использовать при моем доступе к AngularFire. Другой вариант / вопрос заключается в том, как сделать эквивалент afDb.object<IUser>... и оставить <IUser> выключенным, но иметь возможность анализировать результаты объекта AngularFire в моей структуре данных, которая является классом User. Таким образом, разбор будет называть Field<T>.SetValue(); или что-то.

1 Ответ

1 голос
/ 02 апреля 2019

Если вы хотите создать полностью универсальный интерфейс для обработки всех различных объектов с несколькими 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>;
}

Таким образом, вы можете использовать IWhatever с afDb.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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...