Почему этот сопоставленный тип удаляет декоратор `?`? Как добиться подобного результата, не удаляя его?

Проблема

Мы создаем сопоставленный тип, который исключает свойства типа Function. У нашего подхода есть проблема: он также удаляет необязательный декоратор (?) из сопоставленных свойств.

Размножение

Вот упрощенное воспроизведение такого поведения. NoOpMap1 ведет себя так, как мы хотим, а NoOpMap2 имеет проблемное поведение.

type NoOpMap1<T> = { // Good. This one does not remove the ?
    [K in keyof T]: T[K];
};

type Keys<T> = {
    [K in keyof T]: K;
}[keyof T];

type NoOpMap2<T> = { // Problem. This one removes the ?
    [K in Keys<T>]: T[K];
};

Демо

type SomeType = {
    foo?: string,
}

// type SomeTypeNoOpMap1 = { foo?: string; }
type SomeTypeNoOpMap1 = NoOpMap1<SomeType>;

// type SomeTypeNoOpMap2 = { foo: string; }
type SomeTypeNoOpMap2 = NoOpMap2<SomeType>;

NoOpMap1 ведет себя так, как ожидалось. Он сохраняет декоратор ? в свойстве foo. NoOpMap2 удаляет это.

Вопрос

Почему NoOpMap2 удаляется ? декоратор? Как добиться подобного результата, не удаляя его?

Фактический вариант использования

Вот полный тип, который мы пытаемся построить:

type DataPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];

type DataPropertiesOnly<T> = {
  [K in DataPropertyNames<T>]
  : T[K] extends (string | number | boolean) ? T[K]
  : T[K] extends (infer A)[] ? DataPropertiesOnly<A>[]
  : DataPropertiesOnly<T[K]>;
};

Как уже упоминалось, указанный выше тип отвечает за удаление свойств типа Function без удаления ? декоратора из оставшихся свойств.


person Shaun Luttin    schedule 15.05.2019    source источник


Ответы (1)


Если вы хотите сохранить необязательный / только для чтения статус свойств в сопоставленного типа, необходимо убедиться, что компилятор воспринимает сопоставление как гомоморфное . Я знаю два способа сделать это.

Один состоит в том, что отображение должно иметь форму {[K in keyof T]: ...}, где вы напрямую сопоставляете keyof T для некоторого T, общего или конкретного. У вас должно быть что-то вроде in keyof, появляющееся непосредственно в типе, иначе это не будет учитываться.

interface Foo {
    optional?: string;
    readonly viewonly: string;
}

type Homomorphic = { [K in keyof Foo]: 0 };
// type Homomorphic = { 
//   optional?: 0 | undefined; 
//   readonly viewonly: 0; 
// }

type KeyOf<T> = keyof T
type NonHomomorphic = { [K in KeyOf<Foo>]: 0 };
// type NonHomomorphic = { 
//   optional: 0; 
//   viewonly: 0; 
// }

Другой способ сделать это - сопоставить параметр универсального типа K, который был ограничено значением keyof T для другого параметра универсального типа T. Так:

type GenericConstraint<T, K extends keyof T> = { [P in K]: 0 };
type ConstrainedHomomorphic = GenericConstraint<Foo, keyof Foo>;
// type ConstrainedHomomorphic = { 
//   optional?: 0 | undefined; 
//   readonly viewonly: 0; 
// }

type OnlySomeKeysStillHomomorphic = GenericConstraint<Foo, "viewonly">;
// type OnlySomeKeysStillHomomorphic = {
//   readonly viewonly: 0;
// }

Последний метод был специально добавлен для того, чтобы частично сопоставленные типы, такие как Pick<T, K>, были гомоморфными. И именно этот метод вам нужен для того, чтобы ваш реальный вариант использования работал:

// unchanged
type DataPropertyNames<T> = {
    [K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];

// quick abort if T is a function or primitive
// otherwise pass to a homomorphic helper type 
type DataPropertiesOnly<T> =
    T extends Function ? never :
    T extends object ? DPO<T, DataPropertyNames<T>> :
    T

// homomorphic helper type
type DPO<T, KT extends keyof T> = {
    [K in KT]
    : T[K] extends (string | number | boolean) ? T[K]
    : T[K] extends (infer A)[] ? DataPropertiesOnly<A>[]
    : DataPropertiesOnly<T[K]>;
}

Я думаю, это будет действовать так, как ты хочешь. Хорошо, надеюсь, что это поможет; удачи!

person jcalz    schedule 15.05.2019
comment
Вы изучали теорию категорий? - person Shaun Luttin; 15.05.2019
comment
Не формально; Хотелось бы, чтобы у меня был более сильный фон, поскольку он оказывается имеет отношение ко многим вещам, происходящим в системах типов языков компьютерного программирования. - person jcalz; 15.05.2019