Передайте аргумент типа generi c через литеральный объект с разными именами полей для каждого поля, если количество значений полей ограничено 4 - PullRequest
1 голос
/ 11 июля 2020

Что я делаю?

Я хочу сделать отображение объекта поле за полем на объекты некоторого общего типа c. У меня есть 4 случая выполнения (с проверкой времени выполнения и выдачей исключения, если оно не совпадает), которые я использовал в качестве свойств исходного объекта:

  • false преобразуется в Smth<unknown> | undefined
  • true преобразуется в Smth<unknown>
  • [] преобразуется в Smth<unknown> с 0 или более элементами
  • [true] преобразуется в Smth<unknown> с 1 или более элементами

На самом деле это не очень сложно, но я не очень рад, что unknown в общем c везде. В некоторых случаях я хочу указать, что именно payload должно иметь этот Smth (только утверждение во время компиляции, никаких проверок времени выполнения не запланировано). Для этой цели я нашел решение с приведением четырех значений к типам, имеющим дополнительное необязательное поле:

type WithOptPayload<T, P> =  T & { payload?: P }
getColDefsByKinds({ b: false as WithOptPayload<false, { u: 99 }> })

И это действительно отлично работает (игровая площадка) :

Screenshot of inferred type

type WithOptPayload =  T & { payload?: P }

const x = getColDefsByKinds({
    a: false,
    b: false as WithOptPayload,
    c: true,
    d: true as WithOptPayload,
    e: [],
    f: [] as WithOptPayload<[], { w: number }>,
    g: [true],
    h: [true] as WithOptPayload<[true], any>,
});

type IfExtends = L extends R ? Y : N

interface Smth {
    x: string
    payload: P
}

type Kind = false | true | readonly [] | readonly [true]
type Unpack = IfExtends>

function getColDefsByKinds(how: T): { [kind in keyof T]: Unpack> } {
    return null!
}

And a bit более сложная версия с

type WithOptPayload<T, P> = T extends undefined ? T & { payload?: P } | undefined : T & { payload?: P }

type Kind = false | true | undefined | readonly [] | readonly [true]
type Unpack<F extends Kind, Res> = Exclude<IfExtends<F, undefined, undefined> | IfExtends<F, true, Res> | IfExtends<F, false, Res | undefined> | IfExtends<F, boolean | undefined, never, Res[]>, never>

function getColDefsByKinds<T extends { [key: string]: Kind }>(how: T): { [kind in keyof T]: Unpack<T[kind], Smth<Exclude<T[kind], undefined> extends { payload?: infer P } ? P : unknown>> } {
    return null!
}

В чем проблема?

Как видите, в строке

b: false as WithOptPayload<false, { u: 99 }> }

Мне нужно использовать тип false дважды. Это не длинное слово, но есть другая проблема: машинописный текст считает, что true as false в порядке . Таким образом, следующие строки компилируются без ошибок и приводят к несоответствию типов времени выполнения и времени компиляции:

x: false as WithOptPayload<true, { u: 99 }>,
y: true as WithOptPayload<false, { u: 99 }>,
z: [true] as WithOptPayload<[], any>,

Мне нужно, чтобы такие преобразования были ошибочными.

Или, может быть, я можно использовать другой способ передачи типа функции сопоставления. Но есть ограничение: все имена свойств должны быть записаны только один раз, поскольку они отличаются от вызова к вызову и не соответствуют никакому интерфейсу.

...