gpt4 book ai didi

typescript - 特定属性上的深度/递归 Required

转载 作者:行者123 更新时间:2023-12-03 19:44:17 25 4
gpt4 key购买 nike

给定一个这样的类:

class Example {
always: number;
example?: number;

a?: {
b?: {
c?: number;
}
};

one?: {
two?: {
three?: number;
four?: number;
}
};
}

例如,是否可以将 a.b.cone.two.three 标记为非可选(必需)属性,而不更改 example 并且可能也不更改 one.two.four

我想知道 ts-essentials 是否有一些递归版本的 MarkRequired

用例:

我们有一个类似 ReST 的 API,它返回数据,其中一些属性总是被定义,而其他属性是可选的并且由客户端明确请求(使用像 ?with=a,b,c.d.e 这样的查询字符串)。我们希望能够将请求的属性和嵌套属性标记为不包括 undefined ,以避免进行不必要的 undefined 检查。

这样的事情可能吗?

最佳答案

所以这就是我想出的创建递归 DeepRequired 类型的方法。

输入

两个泛型类型参数:

  • T 用于基本类型 Example
  • P 用于联合类型的元组,代表我们的“必需的对象属性路径” ["a", "b", "c"] | ["one", "two", "three"](类似于通过 get 的 lodash 对象路径)

  • 示例流程
  • 在顶级 P[0] 中获取所有必需的属性: "a" | "one"
  • 创建必需和非必需对象属性的交集类型/串联

  • 我们包含 Example 中的所有属性,并另外创建一个 mapped type 以删除 ? 和每个要更改为必需属性的可选属性的 undefined 值。我们可以通过使用内置类型 RequiredNonNullable 来做到这一点。
    type DeepRequired<T, P extends string[]> = T extends object
    ? (Omit<T, Extract<keyof T, P[0]>> &
    Required<
    {
    [K in Extract<keyof T, P[0]>]: NonNullable<...> // more shortly
    }
    >)
    : T;
  • 子属性的类型必须以某种方式递归。这意味着,我们还必须找到一种方法来从元组 T 中“移动”类型,以迭代地获取路径中的下一个所需子属性。为此,我们创建了一个辅助元组类型 Shift(稍后将详细介绍实现)。
  • type T = Shift<["a", "b", "c"]> 
    = ["b", "c"]
  • 具有挑战性的事情是,我们想要传递元组的联合(也就是许多必需的路径),而不仅仅是一个。我们可以为此使用 distributive conditional types 并使用另一个帮助程序 ShiftUnion 能够在包含 Shift 的条件类型上分发元组联合:
  • type T = ShiftUnion<["a", "b", "c"] | ["one", "two", "three"]> 
    = ["b", "c"] | ["two", "three"]
  • 然后我们可以通过简单地选择第一个索引来获取下一个子路径的所有必需属性:
  • type T = ShiftUnion<["a", "b", "c"] | ["one", "two", "three"]>[0] 
    = "b" | "two"

    执行

    主要类型 DeepRequired
    type DeepRequired<T, P extends string[]> = T extends object
    ? (Omit<T, Extract<keyof T, P[0]>> &
    Required<
    {
    [K in Extract<keyof T, P[0]>]: NonNullable<
    DeepRequired<T[K], ShiftUnion<P>>
    >
    }
    >)
    : T;

    元组辅助类型 Shift/ ShiftUnion
    我们可以在 generic rest parameters in function typestype inference in conditional types 的帮助下推断元组类型,即移动了一个元素。
    // Analogues to array.prototype.shift
    export type Shift<T extends any[]> = ((...t: T) => any) extends ((
    first: any,
    ...rest: infer Rest
    ) => any)
    ? Rest
    : never;

    // use a distributed conditional type here
    type ShiftUnion<T> = T extends any[] ? Shift<T> : never;

    测试
    type DeepRequiredExample = DeepRequired<
    Example,
    ["a", "b", "c"] | ["one", "two", "three"]
    >;

    declare const ex: DeepRequiredExample;

    ex.a.b.c; // (property) c: number
    ex.one.two.three; // (property) three: number
    ex.one.two.four; // (property) four?: number | undefined
    ex.always // always: number
    ex.example // example?: number | undefined

    Playground

    一些波兰语(更新)

    还有一些小的不准确之处:如果我们也在 two 下添加属性 a ,例如 a?: { two?: number; ... }; ,它也被标记为需要,尽管在我们的路径 P["a", "b", "c"] | ["one", "two", "three"] 在示例中。我们可以通过扩展 ShiftUnion 类型轻松解决这个问题:
    type ShiftUnion<P extends PropertyKey, T extends any[]> = T extends any[]
    ? T[0] extends P ? Shift<T> : never
    : never;

    例子:
    // for property "a", give me all required subproperties
    // now omits "two" and "three"
    type T = ShiftUnion<"a", ["a", "b", "c"] | ["one", "two", "three"]>;
    = ["b", "c"]

    此实现不包括同名属性,如 two ,它们位于不同的“对象路径”中。所以 two 下的 a 不再被标记为required。

    Playground

    可能的扩展
  • 对于单个必需的属性,为方便起见,传入字符串而不是元组路径。
  • 当前实现适用于少数需要标记的对象路径;如果要选择一个对象的多个嵌套子属性,则可以扩展该解决方案以接收对象文字类型而不是元组。


  • 希望,这有帮助!随意使用它作为您进一步实验的基础。

    关于typescript - 特定属性上的深度/递归 Required<T>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57835286/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com