打字稿类型的可选链接

Optional chaining for typescript types

提问人:Hoffmann 提问时间:11/26/2020 最后编辑:Oleg Valter is with UkraineHoffmann 更新时间:5/26/2022 访问量:631

问:

我有一个深度嵌套的数据结构,我希望能够在其中引用内部类型,但该类型没有自己的名称/定义。例如:

MyQuery['system']['errors']['list'][number]

我使用 graphql-codegen 从 graphql 查询中自动生成类型。我想要单一的类型,但有两个问题:MyQueryerror

  1. 中间的所有这些值都是可为 null 的
  2. 我在自动生成的类型中没有错误的唯一名称

我尝试了以下方法:

  1. 有效,但真的很难读:
type Error = NonNullable<NonNullable<NonNullable<MyQuery>['system']>['errors']>['list'][number]
  1. 不起作用(也不起作用)?.['field']
type Error = MyQuery?['system']?['errors']?['list']?[number]
  1. 有效,但会创建不必要的变量:
const error = queryResult?.system?.errors?.list?.[0]
type Error: typeof error
  1. 有点工作,但是 Error 中的字段也变得不为空,我不想要
import { DeepNonNullable } from 'utility-types'

type Error = DeepNonNullable<MyQuery>['system']['errors']['list'][number]

基本上,我要问的是,是否有一种更简单的方法可以在打字稿中执行“类型的可选链接”。我的 API 非常容易出现 null,如果我能比使用多个更容易做到这一点,那将非常有用NonNullable<T>

typescript 可选链接 嵌套类型

评论

0赞 T.J. Crowder 11/26/2020
“我从 graphql 查询自动生成 MyQuery。”你这是什么意思?您从 graphql 查询创建类型?这看起来如何,输出源代码或其他东西?
1赞 Hoffmann 11/26/2020
@T.J.Crowder是的,就是这样,我使用 graphql-codegen 和 graphql-codegen-typescript。查看 graphql-code-generator.com
0赞 T.J. Crowder 11/26/2020
有趣!...
0赞 Murali N 4/13/2022
“有效,但真的很难阅读” - 创建多种类型,至少它很容易阅读。

答:

5赞 Oleg Valter is with Ukraine 5/26/2022 #1

如果有更简单的方法来执行“类型的可选链接”

不,不幸的是,到目前为止,还没有本地方法来“选择性地链接”深度嵌套类型。然而,有一种相当迂回的方法可以通过复杂的递归条件泛型类型和路径来模拟它。首先,您需要一个可重用的帮助程序来处理索引签名:

type _IndexAccess<T, U extends keyof T, V extends string> = V extends "number" 
    ? Exclude<T[U], undefined> extends { [x:number]: any } ? 
        Exclude<T[U], undefined>[number]
        : undefined
    : V extends "string" ?
        Exclude<T[U], undefined> extends { [x:string]: any } ?
            Exclude<T[U], undefined>[string]
            : undefined
    : V extends "symbol" ?
        Exclude<T[U], undefined> extends { [x:symbol]: any } ?
            Exclude<T[U], undefined>[symbol]
            : undefined
    : undefined;

然后,您可以创建一个帮助程序类型,用于递归遍历嵌套类型,依赖于模板文本类型来处理路径:infer

type DeepAccess<T, K extends string> = K extends keyof T 
    ? T[K] 
    : K extends `${infer A}.${infer B}` 
        ? A extends keyof T 
            ? DeepAccess<Exclude<T[A], undefined>, B>
            : A extends `${infer C}[${infer D}]`
                ? DeepAccess<_IndexAccess<T, C extends keyof T ? C : never, D>, B>
                : undefined
    : K extends `${infer A}[${infer B}]` 
        ? A extends keyof T 
            ? B extends keyof T[A] 
                ? T[A][B] 
                : _IndexAccess<T, A, B>       
            : undefined
    : undefined;

它并不漂亮,但它允许优雅地透镜到嵌套类型:

type MyQuery = {
    system?: {
        errors?: {
            list?: [{
                answer: 42,
                questions: { known: false }[]
            }]
        }
    }
};

// false
type t1 = DeepAccess<MyQuery, "system.errors.list[number].questions[number].known">;

// [{ answer: 42; questions: { known: false; }[]; }] | undefined
type t2 = DeepAccess<MyQuery, "system.errors.list">;

// 42
type t3 = DeepAccess<MyQuery, "system.errors.list[number].answer">;

// undefined
type t4 = DeepAccess<MyQuery, "system.errors.list.unknown">;