嵌套对象类型解构

Nest object type destructuring

如何从嵌套对象中获取类型定义?

在这个例子中,我得到了一个实体(产品)列表,我如何访问实体(节点)的类型定义。

这是使用relay-compiler

编译的
export type ProductAutoSuggestQueryResponse = {
     node: {
         products?: {
             edges: ReadonlyArray<{
                 node: {
                     name: string;
                     id: string;
                     currencies: ReadonlyArray<string> | null;
                };
            } | null> | null;
        } | undefined;
    } | null;
};

我尝试过使用 Pick<>,但它似乎变得非常复杂,数组导致我出现问题。

从概念上讲,您想深入了解 ProductAutoSuggestQueryResponseindex into it。让我们假设这种类型是:

type ProductAutoSuggestQueryResponse = {
    node: {
        products: {
            edges: readonly {
                node: {
                    name: string;
                    id: string;
                    currencies: readonly string[];
                };
            }[];
        };
    };
}

给定 ProductAutoSuggstQueryResponse 的修改版本的值 v,您可以通过读取 v.node.products.edges[0].node 来获得所需节点类型的值。更一般地说,当 p'node' 类型并且 q'products'r 类型时,您可以通过阅读 v[p][q][r][i][s] 来获得这样的值是 'edges' 类型,inumber 类型,s'node' 类型。这种索引可以在类型级别使用索引访问类型完成:

type NodeType =
  ProductAutoSuggestQueryResponse['node']['products']['edges'][number]['node'];
/* type NodeType = {
    name: string;
    id: string;
    currencies: readonly string[];
} */

这就是你想要的。


太好了。不幸的是,您的 ProductAutoSuggestQueryResponse 类型与我上面写的不完全相同。通过对象结构的各个层级,直接有optional or otherwise nullable properties, meaning that they can include undefined or null. Instead of {a: string}, you've got {a: string} | undefined or {a: string} | null or {a: string} | undefined | null. You can't safely index into something which might be undefined or null, so the compiler won't let you use indexed access types

可以做的是在索引之前使用the NonNullable<T> utility type{a: string} | undefined | null之类的东西变成{a: string}。这可以手动完成,但很烦人:

type NodeTypeManual = NonNullable<
    NonNullable<
        NonNullable<
            NonNullable<
                ProductAutoSuggestQueryResponse['node']
            >['products']
        >['edges']
    >[number]
>['node']

/* type NodeTypeManual = {
    name: string;
    id: string;
    currencies: ReadonlyArray<string> | null;
} */

如果你发现你要经常这样做,你可以写一个索引的递归conditional type that operates on a tuple

type NonNullableDeepIndex<T, KT extends PropertyKey[]> =
    KT extends [infer KF, ...infer KR] ? (
        KF extends keyof NonNullable<T> ? (
            NonNullableDeepIndex<NonNullable<T>[KF], Extract<KR, PropertyKey[]>>
        ) : never
    ) : NonNullable<T>;

类型函数NonNullableDeepIndex接受一个类型T和一个键元组KT。如果键元组为空,我们只是 return NonNullable<T>。否则,我们获取第一个键 KF 并使用 KF 索引到 NonNullable<T>,然后获取新类型和键元组的其余部分 KR,并递归计算 NonNullableDeepIndex.

那么我们可以将NodeType表达得少一些,如:

type NodeType = NonNullableDeepIndex<
    ProductAutoSuggestQueryResponse,
    ["node", "products", "edges", number, "node"]
>;
/* type NodeType = {
    name: string;
    id: string;
    currencies: ReadonlyArray<string> | null;
} */

Playground link to code