繁体   English   中英

如何 select 来自可区分联合类型的密钥?

[英]How to select a key from a discriminated union type?

我有一个受歧视的工会

type MyDUnion = { type: "anon"; name: string } | { type: "google"; idToken: string };

我想直接从MyDUnion类型的判别联合访问名称键。 像这样的东西

type Name = MyDUnion['name']

但是 typescript 不允许

Property 'name' doesn't exist on type '{ type: "anon"; name: string } | { type: "google"; idToken: string }'

我怎样才能访问它?

需要明确的是,这不是一个有效的解决方案:

type MyName = string;
type MyDUnion = { type: "anon"; name: MyName } | { type: "google"; idToken: string };
type Name = MyName; // this line would be in a different file

这是无效的,因为那样的话我将不得不导出MyNameMyDUnion类型以便在别处使用。

有任何想法吗?

为了过滤对象的联合,通常你需要使用Extract :最简单的方法:

type Result = Extract<MyDUnion , {type: "anon"}>

更健壮的:

type MyDUnion = { type: "anon"; name: string } | { type: "google"; idToken: string };

type Filter<Union, Type extends Partial<Union>> = Extract<Union, Type>

type Result = Filter<MyDUnion, { type: 'anon' }>

通用解决方案

export type PickAll<
    Union,
    Keys extends KeyOf<Union>,
    Otherwise = undefined
> = {
    [_K in Keys]: Union extends { [K in _K]: infer Value }
        ? UnionToIntersection<Value>
        : Otherwise
}

帮手

type KeyOf<Union, Otherwise = never> = Union extends Union
    ? keyof Union
    : Otherwise
type UnionToIntersection<U> = (
    U extends any ? (k: U) => void : never
) extends (k: infer I) => void
    ? I
    : never

目标

type MyDUnion =
  | { type: 'anon'; name: string }
  | { type: 'google'; idToken: string }

indexed accesskeyof

MyDUnion['type']
/* "anon' | "google" */
// OK

MyDUnion[keyof MyDUnion]
/* "anon" | "google" */
// ❓

// @ts-expect-error - not all union members have an idToken
MyDUnion['type' | 'idToken']
/* any */
// ❌

KeyOf<Union>

type UnionKeys = KeyOf<MyDUnion>
/* "type" | "name" | "idToken" */
// ✅

PickAll<Union, KeyOf<Union>?, Otherwise?>

默认情况下,选择所有

type DUnionProps = PickAll<MyDUnion>
/* {
  type: "anon" | "google";
  name: string | undefined;
  idToken: string | undefined;
} */
// ✅

专注于特定的Key (+IDE 提示和类型检查)

ctrl + space是OP

type DUnionName = PickAll<MyDUnion, 'name'>
/* { 
  name: string | undefined
} */
// ✅

Keys的联合

type DesiredProps = PickAll<
    MyDUnion | { fish: number },
    'type' | 'idToken' | 'fish'
>
/* {
    type: "anon" | "google" | undefined;
    idToken: string | undefined;
    fish: number | undefined;
} // ✅ */

陷阱

  • 直接提取文字。

不要这样做:

type应该是"anon" | "google"

type GoogleLike = PickAll<MyDUnion, 'type' | 'name'>

type g = GoogleLike['name' | 'type']
/* string | undefined 🥴 */

这样

type GoogleLikeTypes = GoogleLike['type']
/* "anon" | "google" */
// ✅

type GoogleLikeNames = GoogleLike['name']
/* string | undefined */
// ✅ (weird, be mindful)

编辑

我忘了提到第三个参数可以用来改变回退类型。 默认是未定义的,我认为这是最类型安全的行为,但你也可以将其设置为任何你想要的。 例如, PickAll<Union, Keys, never>等同于Required<PickAll<Union, Keys> >,或者至少如果Required可以推断出像PickAll这样的类型,那么它会是等价的。

为了获得所需的密钥,您必须以某种方式告诉 Typescript 编译器

嘿编译器,我想知道这个 object,在一个非常具体的情况下

在这种情况下,当type ==='anon'时,您想了解 object。 所以,以你的例子,

type MyDUnion = { type: "anon"; name: string } | { type: "google"; idToken: string };

type SpclCase = MyDUnion & {type: "anon"}

通过这样做,您将获得有关这两种情况何时重叠的信息。 现在您可以直接索引name键。

type Name = SpclCase['name']

如果你想让它更健壮,我们可以确保我们使用的缩小类型(在本例中为{type: 'anon'} )满足所需的形状。 这是一个临时的解决方案。

type DiscriminatorObj = { type: 'anon' }
type RequiredCase = DiscriminatorObj extends Pick<MyDUnion, "type"> ? DiscriminatorObj: never;
type SpclCase = (RequestBody & RequiredCase);
type Name = SpclCase['name']

我知道它的边缘有点粗糙,但你总是可以将它提取到通用函数中并随意使用它们。 我只是向你展示了基本逻辑。 您可以使用它使它更美观。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM