39

关于如何递归地将 TypeScript 的 Partial 映射类型应用于接口,同时不破坏任何具有数组返回类型的键的任何想法?

以下方法还不够:

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[];
}>

像这样

4

4 回答 4

87

使用 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]>
};

或者用[]而不是Array<>

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends (infer U)[]
    ? DeepPartial<U>[]
    : T[P] extends Readonly<infer U>[]
      ? Readonly<DeepPartial<U>>[]
      : DeepPartial<T[P]>
};

您可能需要查看https://github.com/krzkaczor/ts-essentials包以了解此包和其他一些有用的类型。

于 2018-04-20T07:51:26.560 回答
12

更新 2018-06-22:

这个答案是在一年前写的,在 TypeScript 2.8 中发布了惊人的条件类型功能之前。所以不再需要这个答案。请参阅下面的@krzysztof-kaczor 的新答案,了解在 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>计算结果为AIfElse<False,A,B>计算结果为B

现在我们定义一个记录类型Rec<K,V,X>,一个具有键值类型的对象KV其中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>>>

此时我们可以获取到你的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>

丑陋,对吧?但我们最终可以将User和描述DeepPartialUser为:

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

你去吧。希望有帮助!

于 2017-07-28T18:40:05.917 回答
2

我从@krzysztof的答案开始,但是当我遇到边缘情况时一直在迭代它。特别是下面的边缘情况,基于基础对象的给定值(即T[P]):

  • any
  • any[]
  • ReadonlyArray<any>
  • Map
  • Set
type NonAny = number | boolean | string | symbol | null;
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends NonAny[] // checks for nested any[]
    ? T[P]
    : T[P] extends ReadonlyArray<NonAny> // checks for nested ReadonlyArray<any>
    ? T[P]
    : T[P] extends (infer U)[]
    ? DeepPartial<U>[]
    : T[P] extends ReadonlyArray<infer U>
    ? ReadonlyArray<DeepPartial<U>>
    : T[P] extends Set<infer V> // checks for Sets
    ? Set<DeepPartial<V>>
    : T[P] extends Map<infer K, infer V> // checks for Maps
    ? Map<K, DeepPartial<V>>
    : T[P] extends NonAny // checks for primative values
    ? T[P]
    : DeepPartial<T[P]>; // recurse for all non-array and non-primative values
};

NonAny类型用于检查any

于 2020-04-15T16:25:04.813 回答
0

您可以使用ts-toolbelt它可以对任何深度的类型进行操作

在您的情况下,它将是:

import {O} from 'ts-toolbelt'

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

type optional = O.Optional<User, keyof User, 'deep'>

如果您想深入计算它(出于显示目的),您可以使用Compute

于 2019-06-19T20:56:48.093 回答