Несколько дженериков в одном интерфейсе

Я пытаюсь сделать какой-то синтаксический анализатор, который анализирует поля данных и превращает их в полную форму, а также отображает ее. Свойство fields будет определять каждое поле в массиве данных json, которое клиент получит от свойства url в IForm

Пример интерфейса формы выглядит следующим образом:

export interface IForm {
    name: string;
    url: string;
    fields: IField<any>[] // <-- What is the proper type of this?
}

export interface IField<T> {
    name: string;
    label: string;
    mandatory?: boolean;
    default: T;
}

export interface IInput<T> extends IField<T> {
    type: 'input'
}

export interface IOptionsUrl {
    url: string;
    idField: string;
    labelField: string;
}

export interface IOptionsList<T> {
    id: T;
    label: string;
}

export interface IOptions<T> extends IField<IOptionsList<T>> {
    type: 'options';
    options?: IOptionsUrl | IOptionsList<T>[] | string[];
    multiple?: boolean;
}

export interface ICheckbox extends IField<boolean> {
    type: 'checkbox'
}

Свойство fields в IForm будет содержать несколько типов, например IField<string> или IField<number> и так далее. Они определяются свойством type в каждом типе, все основано на IField. Поэтому я не уверен, стоит ли мне ставить <any>, поскольку он будет содержать несколько типов данных в массиве. Каков правильный способ его определения? Или я должен пропустить все дженерики и просто использовать любой?

Примерные данные будут такими:

let meta: IForm = {
    name: 'employee',
    url: '/api/employee',
    fields: [
        {
            type: 'input',
            name: 'id',
            label: 'Employee ID',
            default: 0,
            mandatory: true
        },
        {
            type: 'input',
            name: 'name',
            label: 'Employee Name',
            default: '',
            mandatory: true
        },
        {
            type: 'options',
            name: 'gender',
            label: 'Male/Female',
            default: 'male',
            options: ['Male', 'Female']
        },
        {
            type: 'checkbox',
            name: 'active',
            label: 'Active',
            default: true
        },
        {
            type: 'options',
            name: 'department',
            label: 'Department',
            default: 0,
            options: {
                url: '/api/departments',
                idField: 'id',
                labelField: 'name'
            }
        }
    ]
}

С интерфейсом сотрудника будет:

export interface IEmployee {
    id: number;
    name: string;
    gender: string;
    active: boolean;
    department: number;
}

Как мне определить интерфейс для IForm?

Спасибо

IField<any>[] и Array<IField<any>>являются синонимами в соответствии с документацией Array. Вы можете создать свой собственный Тип союза / Псевдоним типа, например type formType = string | number | boolean | date;. Тогда используйте IField<formType>.
Erik Philips 10.06.2019 17:32

Да, но <any> будет содержать все эти строки, числа, логические значения и дату одновременно в одном массиве.

Magician 10.06.2019 17:36
any охватывает более, а не только эти 4, поэтому, если вы хотите обеспечить безопасность типов, не используйте any.
Erik Philips 10.06.2019 17:38

Как вы собираетесь использовать IForm? Какие гарантии вы ожидаете от его fields свойства? Может быть, вы можете отредактировать это в минимальный воспроизводимый пример, чтобы кто-то мог посоветовать вам, что делать. Существуют более строго типизированные решения, в которых IForm сам по себе является общим, но сложность может не потребоваться в зависимости от ваших вариантов использования.

jcalz 10.06.2019 17:39

@jcalz только что добавил образцы данных.

Magician 10.06.2019 17:44

@ErikPhilips Это я знаю. Поэтому я спрашиваю здесь, как я могу указать это, кроме как просто <any>.

Magician 10.06.2019 17:51

Я предполагаю, что вариант использования, который меня интересует, это то, что вы будете делать с meta; в частности свойство meta.fields. Вам нужно, чтобы компилятор помнил, что meta.fields[2].options существует, не проверяя его сначала? Или вы планируете перебирать каждый элемент meta.fields и делать разные вещи в зависимости от его свойства type. В качестве первого предположения я бы сказал, что вы должны скомпилировать объединение возможных фактических типов IField<T>, которые вы будете использовать (например, ICheckBox | IInput<string | number | boolean> | IOptions<string | number | boolean>), и использовать их массив для типа fields.

jcalz 10.06.2019 17:59

@jcalz Понятно.. Спасибо. Думаю, это было бы более уместно. Да, я планирую перебрать каждый элемент и показать правильную форму для этого поля. Параметр meta.fields[2] создаст список из 2 параметров в поле со списком, когда он отображается. Мужчина и женщина. URL-адрес в meta.fields[4] покажет то же самое, но будет получен из `/api/department' для своего списка.

Magician 10.06.2019 18:10

@jcalz, можешь ли ты поставить это как ответ, чтобы я мог его принять?

Magician 10.06.2019 18:22
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
9
48
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Если вы хотите ограничить типы, используйте Тип Союза:

type formType = string | number | boolean | date;
export interface IForm {
  name: string;
  url: string;
  fields: IField<formType>[] // <-- What is the proper type of this?
}



let form.default = "adsf";     //valid
let form.default = 1;          //valid
let form.default = true;       //valid
let form.default = new date(); //valid
let form.default = null;       //in-valid
let form.default = undefined;  //in-valid
let form.default = never;      //in-valid

Итак, могу ли я определить поля: IFields<string> в первом элементе, затем IFields<number> во втором, затем IFields<boolean> в третьем с этим?

Magician 10.06.2019 18:00

Можете ли вы исправить опечатки в этом коде? Он не будет компилироваться. (date предположительно должно быть Date, и я действительно не знаю, что должно быть let form.default =...)

jcalz 10.06.2019 18:02
Ответ принят как подходящий

Я бы посоветовал, по крайней мере, с учетом приведенной выше информации, сделать что-то вроде этого:

type PossibleDataTypes = string | number | boolean; // or whatever you want

type PossibleFields =
  | IInput<PossibleDataTypes>
  | IOptions<PossibleDataTypes>
  | ICheckbox;

export interface IForm {
  name: string;
  url: string;
  fields: Array<PossibleFields>;
}

Здесь мы сужаем field до массива только тех типов полей, которые вы ожидаете. Вы можете дополнить этот список, если хотите.

Кстати, я сделал еще одно изменение:

// changed this from IField<IOptionsList<T>> to just IOptionsList<T>
export interface IOptions<T> extends IField<T> {
  type: "options";
  options?:
    | IOptionsUrl
    | ReadonlyArray<IOptionsList<T>>
    | ReadonlyArray<string>;
  multiple?: boolean;
}

потому что ваша переменная meta не соответствует без нее. Также я думаю, что в meta была опечатка, где использовалось data вместо url. В любом случае вы можете определить meta как IForm, как вы это сделали, но расширение переменной до IForm заставит забыть о деталях (например, какие конкретные типы полей вы используете). Если вы просто хотите проверять, который meta соответствует IForm, не расширяя его до IForm, вы можете использовать такую ​​вспомогательную функцию:

const asIForm = <F extends IForm>(f: F) => f;

А затем используйте его как

const meta = asIForm({
  name: "employee",
  url: "/api/employee",
  fields: [
    {
      type: "input",
      name: "id",
      label: "Employee ID",
      default: 0,
      mandatory: true
    },
    {
      type: "options",
      name: "gender",
      label: "Male/Female",
      default: "male",
      options: ["Male", "Female"]
    },
    {
      type: "checkbox",
      name: "active",
      label: "Active",
      default: true
    },
    {
      type: "options",
      name: "department",
      label: "Department",
      default: 0,
      options: {
        url: "/api/departments",
        idField: "id",
        labelField: "name"
      }
    }
  ]
});

Теперь, учитывая тот факт, что PossibleFields является конкретным дискриминированный союз, вы можете заставить компилятор сузить каждую запись field с помощью защиты типа, например:

function processForm(form: IForm) {
  for (let field of form.fields) {
    switch (field.type) {
      case "input": {
        // do something for input
        break;
      }
      case "checkbox": {
        // do something for checkbox
        break;
      }
      case "options": {
        // do something for options
        field.options // <-- no error, known to exist
        break;
      }
      default:
        ((x: never) => console.info("WHAT IS" + x))(field); // guarantee exhaustive
        // If an error appears here -------------> ~~~~~
        // then you missed a case in the switch statement
    }
  }
}

Хорошо, надеюсь, это поможет вам. Удачи!

Ссылка на код

Вау.. Большое спасибо.. Это бы многое исправило. Я не использую Date, так как у меня есть конкретный IDate для даты с диапазонами и ICheckbox для логического значения. Но мне этого достаточно для работы. Спасибо.

Magician 10.06.2019 18:28

Другие вопросы по теме