Typescript 类型的可选链接

Hof*_*ann 11 typescript optional-chaining nested-types

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

MyQuery['system']['errors']['list'][number]
Run Code Online (Sandbox Code Playgroud)

MyQuery我使用 graphql-codegen 从 graphql 查询自动生成类型。我想要 single 的类型error,但有两个问题:

  1. 中间的所有值都可以为空
  2. 我的自动生成类型中的错误没有唯一的名称

我尝试了以下方法:

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

type Error = DeepNonNullable<MyQuery>['system']['errors']['list'][number]
Run Code Online (Sandbox Code Playgroud)

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

Ole*_*ter 5

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

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

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;
Run Code Online (Sandbox Code Playgroud)

然后,您可以创建一个辅助类型,用于递归遍历嵌套类型依赖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;
Run Code Online (Sandbox Code Playgroud)

它并不漂亮,但它允许优雅地转换为嵌套类型:

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">;
Run Code Online (Sandbox Code Playgroud)