如何在不破坏数组属性的情况下实现 TypeScript 深度部分映射类型

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/45372227/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 04:46:23  来源:igfitidea点击:

How to implement TypeScript deep partial mapped type not breaking array properties

typescriptrecursionpartialmapped-types

提问by Allan Simoyi

Any ideas as to how might apply TypeScript's Partial mapped type to an interface recursively, at the same time not breaking any keys with array return types?

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

The following approaches have not been sufficing:

以下方法还不够:

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

Any ideas?

有任何想法吗?

UPDATE: Accepted answer - A better and more general solve for now.

更新:接受的答案 - 现在更好,更通用的解决方案。

Had found a temporary workaround which involves intersection of types and two mapped types as follows. The most notable drawback is that you have to supply the property overrides to restore sullied keys, the ones with array return types.

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

E.g.

例如

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

Like so

像这样

采纳答案by jcalz

UPDATE 2018-06-22:

更新 2018-06-22:

This answer was written a year ago, before the amazing conditional typesfeature was released in TypeScript 2.8. So this answer is no longer needed. Please see @krzysztof-kaczor's new answerbelow for the way to get this behavior in TypeScript 2.8 and up.

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



Okay, here is my best attempt at a crazy but fully general solution (requiring TypeScript 2.4 and up) which might not worth it to you, but if you want to use it, be my guest:

好的,这是我对疯狂但完全通用的解决方案(需要 TypeScript 2.4 及更高版本)的最佳尝试,这对您来说可能不值得,但如果您想使用它,请成为我的客人:

First, we need some type-level boolean logic:

首先,我们需要一些类型级别的布尔逻辑:

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

All you need to know here is that the type IfElse<True,A,B>evaluates to Aand IfElse<False,A,B>evaluates to B.

在这里您需要知道的只是该类型的IfElse<True,A,B>计算结果为A并且IfElse<False,A,B>计算结果为B

Now we define a record type Rec<K,V,X>, an object with key Kand value type V, where Rec<K,V,True>means the property is required, and Rec<K,V,False>means the property is optional:

现在我们定义一个记录类型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>>>


At this point we can get to your Userand DeepPartialUsertypes. Let's describe a general UserSchema<R>where every property we care about is either required or optional, depending on whether Ris Trueor False:

在这一点上,我们可以得到你的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>

Ugly, right? But we can finally describe both Userand DeepPartialUseras:

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

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

And see it in action:

并在行动中看到它:

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

var deepPartialUser: DeepPartialUser = {
  emailAddress: '[email protected]',
  verification: {
    verified: false
  }
} // missing properties are fine, extra will still error


There you go. Hope that helps!

你去吧。希望有帮助!

回答by Krzysztof Kaczor

With TS 2.8 and conditional types we can simply write:

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

You might want to checkout https://github.com/krzkaczor/ts-essentialspackage for this and some other useful types.

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

回答by Nickofthyme

I started with @krzysztof's answer but have since been iterating on it when I come across edge cases. Specifically the edge cases below, based on the given value of the base object (i.e. T[P]):

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

  • any
  • any[]
  • ReadonlyArray<any>
  • Map
  • Set
  • 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
};

The NonAnytype is used to check for anyvalues

NonAny类型被用于检查any

回答by pirix-gh

You can use ts-toolbelt, it can do operations on types at any depth

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

In your case, it would be:

在您的情况下,它将是:

import {O} from 'ts-toolbelt'

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

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

And if you want to compute it deeply (for display purposes), you can use Computefor that

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