#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. Я обновил ответ, см. Нижний раздел. Если будут какие-либо дальнейшие изменения в области применения, я настоятельно рекомендую открыть новый вопрос. Удачи!