如何在TypeScript中创建一个类似于Pick和Partial组合的类型?

5lhxktic  于 2022-12-24  发布在  TypeScript
关注(0)|答案(1)|浏览(130)

我希望有一个类型,我知道特定的属性将被定义,但一些属性将丢失。类似这样:

type UserType = {
  email: string
  name: {
    first: string
    last: string
  }
  address: {
    city: string
    state: string
    zip: string
    coordinates: {
      lat: number
      lng: number
    }
  }
}

const partialUser: PickPartial<
  UserType,
    | 'email'
    | Record<
      'address',
      Record<
        'coordinates',
          | 'lat'
          | 'lng'
      >
    >
>

基本上我是这样选择的:

{
  email: string
  name?: {
    first: string
    last: string
  }
  address: {
    city?: string
    state?: string
    zip?: string
    coordinates: {
      lat: number
      lng: number
    }
  }
}

这可能吗?我看到了PartialDeep代码,但它并不像我想的那样工作。我本质上想说的是,树中的这些特定属性是定义的(不可能是未定义的),其余的可能是未定义的?。我如何在TypeScript中完成这一点?如果不完全正确,我可以得到什么最接近这一点,或者有什么解决方案?
另一种API方法可能是:

const partialUser: PickPartial<
  UserType,
  {
    email: string
    address: {
      coordinates: {
        lat: number
        lng: number
      }
    }
  }
>

然后其他所有东西都得到一个?可能未定义的键。有可能以某种方式做到这一点吗?

jdgnovmf

jdgnovmf1#

首先,我倾向于使用这样的结构

PickPartial<
  UserType, 
  { email: 1, address: { coordinates: { lat: 1, lng: 1 } } }
>

其中潜在的嵌套键集由单个对象表示,拥有与您所关心的相同的嵌套键。嵌套的 values 并不重要,只要它们本身不是对象类型(否则 * 他们的 * 键将被探测)。我选择上面的1是因为它很短,但是你可以使用string或者number或者其他什么,我建议使用这种表示法是因为它一致地把键当作键,而不是有时把字符串类型当作空的。
总之,使用它,我们可以写PickPartial<T, K>,就像

type PickPartial<T, M> = (
  Partial<Omit<T, keyof M>> & 
  { [K in keyof T & keyof M]: 
      M[K] extends object ? PickPartial<T[K], M[K]> : T[K]
  }
);

首先,Partial<Omit<T, keyof M>>意味着T中没有M中的键的任何部分(使用Omit<T, K>实用程序类型)将成为部分(使用Partial<T>实用程序类型)。然后,{ [K in keyof T & keyof M]: M[K] extends object ? PickPartial<T[K], M[K]> : T[K] }maps也在M中的T的密钥以及递归地PickPartial的它们(如果Map值类型本身是一个对象)或仅保留T中的类型(如果Map值类型不是对象,如上面的1)。
这是可行的,但会产生难以检查的类型:

type Z = PickPartial<UserType, { email: 1, address: { coordinates: { lat: 1, lng: 1 } } }>
/* type Z = Partial<Omit<UserType, "email" | "address">> & {
    email: string;
    address: PickPartial<{
        city: string;
        state: string;
        zip: string;
        coordinates: {
            lat: number;
            lng: number;
        };
    }, {
        coordinates: {
            lat: 1;
            lng: 1;
        };
    }>;
} */

你想要这种人吗?很难说。
为了弥补这一点,我将使用How can I see the full expanded contract of a Typescript type?中的一种技术,即获取基本类型,通过条件类型推理将其复制到新的类型参数中,然后在其上进行标识Map。

type Foo = SomethingUgly

你可以得到更好的结果

type Foo = SomethingUgly extends infer O ? {[K in keyof O]: O[K]} : never;

因此,我们得到:

type PickPartial<T, M> = (
    Partial<Omit<T, keyof M>> & {
        [K in keyof T & keyof M]:
        M[K] extends object ? PickPartial<T[K], M[K]> : T[K]
    }
) extends infer O ? { [K in keyof O]: O[K] } : never;

现在,如果我们尝试一下,我们会得到:

type Z = PickPartial<UserType, { email: 1, address: { coordinates: { lat: 1, lng: 1 } } }>
/* type Z = {
    name?: {
        first: string;
        last: string;
    };
    email: string;
    address: {
        city?: string;
        state?: string;
        zip?: string;
        coordinates: {
            lat: number;
            lng: number;
        };
    };
} */

你想要的那种。
Playground代码链接

相关问题