как реализовать TypeScript глубокий частичный сопоставленный тип, не нарушающий свойства массива

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

следующих подходов было недостаточно:

interface User {  
  emailAddress: string;  
  verification: {
    verified: boolean;
    verificationCode: string;
  }
  activeApps: string[];
}

type PartialUser = Partial<User>; // does not affect properties of verification  

type PartialUser2 = DeepPartial<User>; // breaks activeApps' array return type;

export type DeepPartial<T> = {
  [ P in keyof T ]?: DeepPartial<T[ P ]>;
}

какие идеи?

обновление: Принятый ответ-лучшее и более общее решение на данный момент.

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

Э. Г.

type PartialDeep<T> = {
  [ P in keyof T ]?: PartialDeep<T[ P ]>;
}
type PartialRestoreArrays<K> = {
  [ P in keyof K ]?: K[ P ];
}

export type DeepPartial<T, K> = PartialDeep<T> & PartialRestoreArrays<K>;

interface User {  
 emailAddress: string;  
 verification: {
   verified: boolean;
   verificationCode: string;
 }
 activeApps: string[];
}

export type AddDetailsPartialed = DeepPartial<User, {
 activeApps?: string[];
}>

вот так

2 ответов


обновление 2018-06-22:

этот ответ был написан год назад, перед удивительными условных типов функция была выпущена в TypeScript 2.8. Поэтому этот ответ больше не нужен. Пожалуйста, см. @Кшиштоф-Качор это ответ ниже для способа получить это поведение в TypeScript 2.8 и выше.


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

во-первых, нам нужна логическая логика на уровне типа:

type False = '0'
type True = '1'
type Bool = False | True
type IfElse<Cond extends Bool, Then, Else> = {'0': Else; '1': Then;}[Cond];

все, что вам нужно знать, это то, что тип IfElse<True,A,B> значение A и IfElse<False,A,B> оценивает в B.

теперь определим тип записи Rec<K,V,X> объект с ключом K и значение типа V, где Rec<K,V,True> означает свойство требуются и Rec<K,V,False> означает свойство дополнительно:

type Rec<K extends string, V, Required extends Bool> = IfElse<Required, Record<K, V>, Partial<Record<K, V>>>

на данный момент мы можем добраться до вашего User и DeepPartialUser типы. Опишем общие UserSchema<R> где каждое свойство, о котором мы заботимся, является обязательным или необязательным, в зависимости от того,R и True или False:

type UserSchema<R extends Bool> =
  Rec<'emailAddress', string, R> &
  Rec<'verification', (
    Rec<'verified', boolean, R> &
    Rec<'verificationCode', string, R>
  ), R> &
  Rec<'activeApps', string[], R>

некрасиво, верно? Но мы можем, наконец, описать оба User и DeepPartialUser as:

interface User extends UserSchema<True> { } // required
interface DeepPartialUser extends UserSchema<False> { }  // optional

и увидеть его в действии:

var user: User = {
  emailAddress: 'foo@example.com',
  verification: {
    verified: true,
    verificationCode: 'shazam'
  },
  activeApps: ['netflix','facebook','angrybirds']
} // any missing properties or extra will cause an error

var deepPartialUser: DeepPartialUser = {
  emailAddress: 'bar@example.com',
  verification: {
    verified: false
  }
} // missing properties are fine, extra will still error

там вы идете. Надеюсь, это поможет!


С TS 2.8 и условными типами мы можем просто написать:

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends Array<infer U>
    ? Array<DeepPartial<U>>
    : T[P] extends ReadonlyArray<infer U>
      ? ReadonlyArray<DeepPartial<U>>
      : DeepPartial<T[P]>
};

вы можете проверить https://github.com/krzkaczor/ts-essentials пакет для этого и некоторых других полезных типов.