Вывод вложенных типов значений с учетом промежуточных необязательных ключей - PullRequest
3 голосов
/ 26 марта 2020

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

type Foo = { a: { b?: number; } };

type Foo2 = { a?: { b: number } };

Для моих целей тип b в Foo и Foo2 должен быть выведен как number | undefined. В Foo2 b сам по себе не является обязательным, а потому что a для моих целей поиска b теперь тоже должен быть необязательным ... так много для контекста.

Использование этих вспомогательных типов (извлечено из большего набора ) в качестве строительных блоков:

type Keys<T> = keyof Required<T>;

type IsOpt<T> = T extends undefined ? true : never;

type HasOptKey1<T, A> = A extends Keys<T> ? IsOpt<T[A]> : never;

type HasOptKey2<T, A, B> = A extends Keys<T>
    ? IsOpt<T[A]> extends never
        ? HasOptKey1<T[A], B>
        : true
    : never;

type Val1<T, A> = A extends Keys<T> ? T[A] : never;

type Val2<T, A, B> = A extends Keys<T> ? Val1<Required<T>[A], B> : never;

Используя их для хорошего использования, мы получаем:

type F1 = HasOptKey1<Foo, "a">; // never - CORRECT!
type F2 = HasOptKey1<Foo2, "a">; // true - CORRECT!
type F3 = HasOptKey2<Foo, "a", "b">; // true - CORRECT!
type F4 = HasOptKey2<Foo2, "a", "b">; // true - CORRECT!

// infer type of `a` in Foo
type A1 = HasOptKey1<Foo, "a"> extends never
  ? Val1<Foo, "a">
  : Val1<Foo, "a"> | undefined;
// { b: number | undefined; } - CORRECT!

// infer type of `a` in Foo2
type A2 = HasOptKey1<Foo2, "a"> extends never
  ? Val1<Foo2, "a">
  : Val1<Foo2, "a"> | undefined;
// { b: number } | undefined - CORRECT!

// infer type of `b` in Foo
type B1 = HasOptKey2<Foo, "a", "b"> extends never
    ? Val2<Foo, "a", "b">
  : Val2<Foo, "a", "b"> | undefined;
// number | undefined - CORRECT!

// infer type of `b` in Foo2
type B2 = HasOptKey2<Foo2, "a", "b"> extends never
    ? Val2<Foo2, "a", "b">
  : Val2<Foo2, "a", "b"> | undefined;
// number | undefined - CORRECT!

Чтобы избежать этих повторяющихся условий, Я хотел использовать другой тип помощника:

// helper type w/ same logic as used for A1/A2/B1/B2 conditionals
type OptVal<PRED, RES> = PRED extends never ? RES : RES | undefined;

// applied
type OptVal1<T, A> = OptVal<HasOptKey1<T, A>, Val1<T, A>>;

type OptVal2<T, A, B> = OptVal<HasOptKey2<T, A, B>, Val2<T, A, B>>;

Однако, несмотря на то, что он работает в 3 из 4 случаев, A3 неправильно выводится как never, и я не понимаю, почему :

type A3 = OptVal1<Foo, "a">;
// never - WHHHYYY??? (should be same as A1!) <-----

type A4 = OptVal1<Foo2, "a">;
// { b: number } | undefined - CORRECT! (same as A2)

type B3 = OptVal2<Foo, "a", "b">; // number | undefined - CORRECT!

type B4 = OptVal2<Foo2, "a","b">; // number | undefined - CORRECT!

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

1 Ответ

5 голосов
/ 26 марта 2020

Могут быть и другие способы выполнить то, что вы пытаетесь сделать, но непосредственная проблема, с которой вы сталкиваетесь, заключается в том, что вы случайно распространяете свой условный тип в определении OptVal. Поскольку PRED является параметром типа, условная проверка PRED extends never ? RES : RES | undefined в конечном итоге разделит PRED на его члены объединения, оценит условное выражение для каждого члена и объединит все вместе для получения результата. И ваш проблемный случай, когда PRED равен never. Вы можете не думать о never как о типе объединения, но для согласованности компилятор считает его «пустым объединением» , и на выходе также будет пустой союз, то есть never.

Самый простой способ отключить дистрибутивные условные типы - это взять параметр «голый тип» PRED и «одеть» его в одноэлементный тип кортежа, например:

type OptVal<PRED, RES> = [PRED] extends [never] ? RES : RES | undefined;

И это заставит ваши дела работать по вашему желанию, я думаю:

type A3 = OptVal1<Foo, "a">; // { b?: number | undefined; }

Хорошо, надеюсь, это поможет; удачи!

Детская площадка ссылка на код

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...