Создайте сложный тип объединения из другого сложного типа объединения

#typescript

Вопрос:

У меня есть такая структура данных :

 type FirstOne = {
  type: 'first-one',
  uniqueKey1111: 1,
};
type FirstTwo = {
  type: 'first-two',
  uniqueKey2222: 2,
}
type First = {
  type: 'first',
  details: FirstOne | FirstTwo
}
type SecondOne = {
  type: 'second-one',
  uniqueKey3: 3,
};
type SecondTwo = {
  type: 'second-two'
};
type Second = {
  type: 'second',
  details: SecondOne | SecondTwo,
}
type DataType = First | Second;
 

данные имеют тип и подтип, если я выберу первый тип, я не смогу выбрать второй-один подтип,

У меня на второй странице выберите, сначала обратитесь к Data.type, затем к Data.details.введите мне нужно проверить параметры выбора:

У меня есть следующий универсальный для преобразования типа типа данных в SelectOption:

 type SelectOption<T> = { name: string, value: T };
 

Я не знаю, как проверить дополнительные параметры, значение которых зависит от основных параметров выбора

 type SelectOptions = {
  main: SelectOption<DataType['type']>[],
  additional: SelectOption<DataType['details']['type']>[];
}

const options : SelectOptions = {
  main: [{name: 'name', value: 'first'}],
  additional: [{name: '', value: 'second-two'}],
}
 

в настройках теперь установлены недопустимые данные, но typescript не проверяет, какие дополнительные опции имеют опцию, которая несовместима с основной.

Также я думаю, что в настоящее время структура SelectOptions не может выполнить эту проверку.

Я буду благодарен за любую идею для решения проблемы

Я понимаю, какие варианты должны иметь следующую структуру

 const options  = {
  main: [{name: 'name', value: 'first'}, {name: 'name', value: 'second'}],
  additional: {
    first: [{name: '', value: 'first-one'}, {name: '', value: 'first-two'}],
    second: [{name: '', value: 'second-one'}, {name: '', value: 'second-two'}]
  },
};
 

Ответ №1:

Вы не сказали этого прямо, но я предполагаю, что вы хотите SelectOptions выглядеть примерно так:

 type SelectOptions = {
    main: SelectOption<"first">[];
    additional: SelectOption<"first-one">[] | SelectOption<"first-two">[];
} | {
    main: SelectOption<"second">[];
    additional: SelectOption<"second-one">[] | SelectOption<"second-two">[];
}
 

Если это так, вы действительно можете заставить TypeScript вычислять SelectOptions в терминах DataType , но вам нужно сделать это, распределив свое определение по (вложенным) объединениям DataType .

Пропустите этот следующий раздел, если вам просто нужен ответ, а не объяснение:


Я знаю два способа распределения между объединениями; один из них-распределительные условные типы, в которых вы пишете тип формы

 type D<T> = T extends any ? F<T> : never
 

, и компилятор автоматически разберется T на составляющие его объединения перед вычислением; и поэтому D<A | B> будет оценивать F<A> | F<B> . Обратите внимание , что это непросто использовать встроенно; вы не можете писать (A | B) extends any ? F<A | B> : never , потому A | B что это не параметр типа. Вы можете использовать вывод условного типа, как в

 type DAB = (A | B) extends infer T ? T extends any ? F<T> : never : never
 

объявить параметр типа и распределить его по нему; это работает, но сложнее и труднее объяснить тем, кто не знаком с тонкостями условных типов. T

Другой способ-использовать сопоставленные типы, которые вы затем немедленно индексируете, чтобы получить объединение их значений. Это работает только в том случае, если объединение содержит какое-либо буквальное свойство, подобное ключу. Допустим, объединение T имеет свойство с именем "type" литерального типа; тогда вы можете использовать переназначение ключей следующим образом:

 type D<T extends { type: PropertyKey }> = { 
  [U in T as U['type']]: F<U> 
}[T['type']];
 

а потом еще раз D<A | B> оценю, чтобы F<A> | F<B> . Это выглядит более сложным, но на самом деле менее безумно использовать встроенное. Здесь вы можете просто заменить T на A | B :

 type DAB = { [U in (A | B) as U['type']]: F<U> }[(A | B)['type']];
 

В дальнейшем я буду использовать этот последний метод:


Вот определение SelectOptions :

 type SelectOptions = { [DT in DataType as DT['type']]: {
    main: SelectOption<DT['type']>[],
    additional: { [DTD in DT['details']as DTD['type']]:
        SelectOption<DTD['type']>[]
    }[DT['details']['type']]
} }[DataType['type']]
 

Вы можете видеть, что мы распределяем операцию по DataType объединению, и для каждой части DT этого объединения мы распределяем дальше по DT['details'] объединению.

Вы можете убедиться, что он соответствует определению в самом начале. Давайте убедимся, что это работает:

 const badOptions: SelectOptions = { // error!
// -> ~~~~~~~~~~
// Type '"first"' is not assignable to type '"second"'.
    main: [{ name: 'name', value: 'first' }],
    additional: [{ name: '', value: 'second-two' }],
};

const goodOptions: SelectOptions = {
    main: [{ name: 'name', value: 'second' }],
    additional: [{ name: '', value: 'second-two' }],
}; // okay
 

Выглядит неплохо. Теперь неверные данные были отклонены, а правильные данные приняты.

Ссылка на игровую площадку для кода


ОБНОВЛЕНИЕ: новая структура значительно отличается и, вероятно, заслуживает другого вопроса, но я дам ответ здесь без особых подробностей (на данный момент это просто переназначение ключа) и надеюсь, что объем вопроса не изменится в дальнейшем:

 type SelectOptions = {
    main: SelectOption<DataType['type']>[],
    additional: { [DT in DataType as DT['type']]: SelectOption<DT['details']['type']>[] }
}
 

Ссылка на игровую площадку для кода

Комментарии:

1. Я изменяю структуру опций -> параметры const = { основные: [{имя: ‘имя’, значение: ‘первый’}, {имя: ‘имя’, значение: ‘второй’}], дополнительные: { первый: [{имя: «, значение: ‘первый-один’}, {имя: «, значение: ‘первый-два’}], второй: [{имя: «, значение: ‘второй-один’}, {имя: «, значение: ‘второй-два’}] }, }

2. Я обновил ответ, см. Нижний раздел. Если будут какие-либо дальнейшие изменения в области применения, я настоятельно рекомендую открыть новый вопрос. Удачи!