Я собираюсь ответить на урезанную версию вопроса, которая игнорирует определенные определения c класса и разницу между конструкторами со свойствами и экземплярами stati c. Вы можете использовать общую технику, представленную ниже, в полной версии с соответствующими преобразованиями.
Учитывая следующий интерфейс,
interface AttributeInterface {
attrName: string;
required?: boolean;
valueType: StringConstructor | NumberConstructor | BooleanConstructor;
}
Я представлю DataType<T extends AttributeInterface>
, который преобразует T
, a объединение из AttributeInterface
s, к сущности, которую оно представляет. Обратите внимание, что если у вас есть тип массива Arr
, например [Att1, Att2]
, вы можете превратить его в объединение, просмотрев его number
сигнатуру индекса: Arr[number]
равно Att1 | Att2
.
В любом случае, вот оно:
type DataType<T extends AttributeInterface> = (
{ [K in Extract<T, { required: true }>["attrName"]]:
ReturnType<Extract<T, { attrName: K }>["valueType"]> } &
{ [K in Exclude<T, { required: true }>["attrName"]]?:
ReturnType<Extract<T, { attrName: K }>["valueType"]> }
) extends infer O ? { [K in keyof O]: O[K] } : never;
Прежде чем я объясню это, давайте попробуем это на следующих двух интерфейсах:
interface AttrTest extends AttributeInterface {
attrName: "test";
required: true;
valueType: StringConstructor
}
interface Attr2Test extends AttributeInterface {
attrName: "test2";
valueType: NumberConstructor;
}
type Entity = DataType<AttrTest | Attr2Test>;
/* type Entity = {
test: string;
test2?: number | undefined;
} */
Хорошо выглядит.
Итак, объяснение: я беру объединение атрибутов T
и делю его на две части: обязательные атрибуты Extract<T, { required: true }>
и необязательные атрибуты Exclude<T, { required: true }>
, где Extract
и Exclude
являются типами утилит, которые фильтруют объединения.
Единственная разница между обработкой, выполненной для этих двух частей, заключается в том, что отображенный тип для первого требуется (нет ?
в определении), а отображаемый тип для последнего является необязательным (с ?
в определении), по желанию ... Затем я пересекаю их вместе.
В любом случае, для каждого ключа K
в свойстве attrName
этих частей T
значение свойства i тип s ReturnType<Extract<T, { attrName: K }>["valueType"]>
. Extract<T, {attrName: K}>
просто находит одного члена T
с K
в качестве attrName
. Затем мы ищем его свойство "valueType"
, которое, как мы знаем, является одним (или более) из StringConstructor
, NumberConstructor
, BooleanConstructor
.
Оказывается, что каждый из этих типов является вызываемой функцией, которая возвращает примитивный тип данных:
const s: string = String(); // StringConstructor's return type is string
const n: number = Number(); // NumberConstructor's return type is number
const b: boolean = Boolean(); // BooleanConstructor's return type is boolean
, что означает, что мы можем легко получить примитивный тип, используя ReturnType
тип утилиты.
Единственное, что осталось объяснить, это ... extends infer O ? { [K in keyof O]: O[K] } : never
. Это трюк, чтобы взять тип пересечения, такой как {foo: string} & {bar?: number}
, и превратить его в один тип объекта, такой как {foo: string; bar?: number}
.
Опять же, легко преобразовать это в форму, которая принимает тип массива :
type DataTypeFromArray<T extends AttributeInterface[]> = DataType<T[number]>;
type AlsoEntity = DataTypeFromArray<[AttrTest, Attr2Test]>;
/* type AlsoEntity = {
test: string;
test2?: number | undefined;
} */
Что должно помочь в создании решения для классов в вашем примере кода.
Хорошо, надеюсь, это поможет; удачи!
Детская площадка ссылка на код