Возможны ли рекурсивные типы массивов при распознавании объединений?

Я только начинаю открывать для себя TypeScript и проверяю пределы. Я ищу способ сделать интерфейс в виде полей в зависимости от значения одного из его свойств.

Например:

type type RecursiveArray<T> = T | RecursiveArray<T>[];
type allowedTypesString = 'string' | 'email' | 'date' | 'id' | 'number' | 'boolean' | 'object';


interface IOptions {
    type: RecursiveArray<allowedTypesString>,
    required?: boolean,
    defaultValue?: PROVIDED_TYPE,
    expected?: PROVIDED_TYPE[],
    transform?: (value: PROVIDED_TYPE) => any,
    validate?: (value: PROVIDED_TYPE) => boolean,
    regexp?: RegExp,
    min?: number,
    max?: number,
    params?: object,
}

Я хочу, чтобы в IOptions были:

  • regex, только если type - это «строка» или «электронная почта».
  • params только если type - «объект».
  • min и max, только если type - "число и т. Д. ...

Я увидел, что могу использовать различаемые союзы, например, в этот поток, но, как видите, свойство type - это RecursiveArray<allowedTypesString>, что означает, что это может быть одна строка, массив строк , массив строковых массивов, и т. д. ...

Что касается профсоюзов, я мог бы заявить:

interface IOptionsString {
    type: string,
    defaultValue?: string,
    expected?: string[],
    regexp?: RegExp,
}

который будет вызываться, если type является строкой. Но что, если я получаю массив строк или массив строк?

Возможно ли то, что я делаю? В противном случае я буду обрабатывать только один массив, но я хочу знать, возможно ли то, о чем я думаю, в TypeScript.

Спасибо за вашу помощь !


person GaldanM    schedule 26.12.2019    source источник


Ответы (1)


Вы можете делать все, что хотите, с условными типами, если параметризуете IOptions фактическим типом, а затем превратитесь в пересечение общих типов и специальных типов, которые зависят от типа.

Чтобы вернуться к исходным типам (например, PROVIDED_TYPE в вашем примере), вам нужно изменить allowedTypesString на карту со строковых типов на фактические типы, а затем использовать условные типы для повторной модификации (т. Е. Преобразования из значения в тип).

type RecursiveArray<T> = T | RecursiveArray<T>[];

interface TypeStringMap {
  'string': string;
  'email': string;
  'date': Date;
  'id': string;
  'number': number;
  'boolean': boolean;
  'object': object;
}

type RecursiveTypes = RecursiveArray<keyof TypeStringMap>;

type HasRegexp<T extends RecursiveTypes> =
  T extends RecursiveArray<'string' | 'email'> ? { regexp: RegExp } : {}

type HasMinMax<T extends RecursiveTypes> =
  T extends RecursiveArray<'number'> ? { min: number, max: number } : {}

type ReifyRecursiveType<T> =
  T extends keyof TypeStringMap ? TypeStringMap[T]
  : (T extends (infer U)[] ? ReifyRecursiveType<U>[] : never)

type IOptions<T extends RecursiveTypes> = {
  type: T;
  expected?: ReifyRecursiveType<T>[];
  defaultValue?: ReifyRecursiveType<T>,
  transform?: <TResult>(value: ReifyRecursiveType<T>) => TResult,
  validate?: (value: ReifyRecursiveType<T>) => boolean,
} & HasRegexp<T> & HasMinMax<T>

type IStringOptions = IOptions<'string'>; // has `regexp` field
type IStringOrEmailOptions = IOptions<('string' | 'email')>; // has `regexp` field
type IEmailArrayArrayOptions = IOptions<'email'[][]>; // has `regexp` field
type INumberOptions = IOptions<'number'>; // has `min` and `max` fields
person Dmitriy    schedule 26.12.2019
comment
Привет, спасибо за ответ. У меня просто вопрос о полях, в которые я помещаю PROVIDED_TYPE. Как я могу преобразовать строку типа в реальный строковый тип? Кроме того, как я могу определить, является ли это строкой, массивом строк или чем-то еще? Например, если T является массивом строк, я хочу, чтобы поле expected было массивом массива строк. Это возможно ? - person GaldanM; 26.12.2019
comment
@GaldanM Я обновил ответ информацией - person Dmitriy; 27.12.2019
comment
Привет, @Dmitry, большое спасибо за ваше обновление, я думаю, что теперь понимаю TypeScript намного лучше. Но это решение, к сожалению, все еще не работает ... Фактически, IOptions будет использоваться в другом подобном интерфейсе: interface IParams { [key: string]: IOptions } Проблема в том, что IOptions запрашивают тип, поэтому я попытался поставить IOptions<RecursiveTypes>, похоже, что он работает, но при создании объекта типа IParams, если я помещаю type в строку, свойство max разрешено, когда не должно ... Большое спасибо за вашу помощь! - person GaldanM; 06.01.2020
comment
Кроме того, если я поставлю defaultValue как ['foo', 'bar'], когда тип - строка, я не получу сообщения об ошибке, но defaultValue должен быть того же типа, что и type - person GaldanM; 06.01.2020