如何实现TypeScript深部分映射类型而不破坏数组属性

All*_*oyi 26 recursion partial typescript mapped-types

关于如何以递归方式将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 ]>;
}
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

更新:接受的答案 - 现在更好,更一般的解决方案.

我找到了一个临时解决方法,涉及类型和两个映射类型的交集,如下所示.最显着的缺点是你必须提供属性覆盖来恢复被污染的密钥,具有数组返回类型的密钥.

例如

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[];
}>
Run Code Online (Sandbox Code Playgroud)

像这样

Krz*_*zor 62

使用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]>
};
Run Code Online (Sandbox Code Playgroud)

您可能想要查看https://github.com/krzkaczor/ts-essentials包,以及其他一些有用的类型.

  • 这是"简单"的有趣用法!:d (10认同)
  • 我们是否还需要一个`extends object`条件,以便原语不是自己映射的? (3认同)

jca*_*alz 10

更新2018-06-22:

这个答案是在一年前写的,之前是在TypeScript 2.8中发布了惊人的条件类型功能.所以不再需要这个答案了.有关在TypeScript 2.8及更高版本中获取此行为的方法,请参阅下面的@ krzysztof-kaczor的新答案.


好的,这是我最好的尝试一个疯狂但完全通用的解决方案(需要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];
Run Code Online (Sandbox Code Playgroud)

这里你需要知道的是类型的IfElse<True,A,B>计算结果AIfElse<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>>>
Run Code Online (Sandbox Code Playgroud)

在这一点上,我们可以得到你的UserDeepPartialUser类型.让我们描述一下UserSchema<R>我们关心的每个属性是必需的还是可选的,这取决于RTrue或是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>
Run Code Online (Sandbox Code Playgroud)

丑,对吧?但是,我们终于可以同时描述UserDeepPartialUser如下:

interface User extends UserSchema<True> { } // required
interface DeepPartialUser extends UserSchema<False> { }  // optional
Run Code Online (Sandbox Code Playgroud)

并看到它在行动:

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
Run Code Online (Sandbox Code Playgroud)

你去吧 希望有所帮助!