给定一个这样的类:
class Example {
always: number;
example?: number;
a?: {
b?: {
c?: number;
}
};
one?: {
two?: {
three?: number;
four?: number;
}
};
}
Run Code Online (Sandbox Code Playgroud)
例如,是否可以将a.b.c和标记one.two.three为非可选(必需)属性,而无需更改example,也可能无需更改one.two.four?
我想知道是否有一些递归版本的MarkRequired from ts-essentials.
用例:
我们有一个类似 ReST 的 API,它返回数据,其中一些属性总是被定义,而另一些属性是可选的并且由客户端明确请求(使用像 的查询字符串?with=a,b,c.d.e)。我们希望能够将请求的属性和嵌套属性标记为不包括undefined,以避免进行不必要的undefined检查。
这样的事情可能吗?
Ema*_*röm 17
这对我来说效果很好:
//Custom utility type:
export type DeepRequired<T> = {
[K in keyof T]: Required<DeepRequired<T[K]>>
}
//Usage:
export type MyTypeDeepRequired = DeepRequired<MyType>
Run Code Online (Sandbox Code Playgroud)
自定义实用程序类型采用任何类型,并迭代地将其键设置为所需的,并递归调用更深层次的结构并执行相同的操作。结果是一个新类型,深度嵌套类型上的所有参数都设置为必需的。
我从这篇文章中得到了这个想法,他使深度嵌套类型可以为空:
type DeepNullable<T> = {
[K in keyof T]: DeepNullable<T[K]> | null;
};
Run Code Online (Sandbox Code Playgroud)
https://typeofnan.dev/making-every-object-property-nullable-in-typescript/
因此,该方法可用于更改深度嵌套属性的任意属性。
所以这就是我想出的创建递归DeepRequired类型的方法。
两个泛型类型参数:
T 对于基本类型 ExampleP对于联合类型的元组,代表我们的“必需的对象属性路径” ["a", "b", "c"] | ["one", "two", "three"](类似于通过get 的lodash 对象路径)P[0]:"a" | "one"我们包括所有属性,Example并另外创建一个要删除的映射类型?以及undefined要更改为必需的每个可选属性的值。我们可以通过使用内置类型Required和NonNullable.
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;
Run Code Online (Sandbox Code Playgroud)
T以迭代地获取路径中的下一个必需的子属性。为此,我们创建了一个辅助元组类型Shift(稍后将详细介绍实现)。type T = Shift<["a", "b", "c"]>
= ["b", "c"]
Run Code Online (Sandbox Code Playgroud)
ShiftUnion能够在包含Shift以下条件的条件类型上分布元组联合的助手:type T = ShiftUnion<["a", "b", "c"] | ["one", "two", "three"]>
= ["b", "c"] | ["two", "three"]
Run Code Online (Sandbox Code Playgroud)
type T = ShiftUnion<["a", "b", "c"] | ["one", "two", "three"]>[0]
= "b" | "two"
Run Code Online (Sandbox Code Playgroud)
主要类型 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;
Run Code Online (Sandbox Code Playgroud)
元组辅助类型Shift/ShiftUnion
借助函数类型中的通用其余参数和条件类型中的类型推断,我们可以推断出元组类型,即移动了一个元素。
// 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;
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
那里仍留有一些小的不准确:如果我们增加财产two也下a,例如a?: { two?: number; ... };,它也被标记为需要的,尽管我们的道路不会beeingP用["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;
Run Code Online (Sandbox Code Playgroud)
例子:
// 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"]
Run Code Online (Sandbox Code Playgroud)
此实现不包括two在不同“对象路径”中的同名属性,例如。所以twoundera不再标记为 required 了。
希望,这有帮助!随意使用它作为您进一步实验的基础。
我使用模板文字类型增强了 ford64 的答案,以允许使用点分隔的字符串指定路径,这在语法上看起来比键数组更熟悉。它不是 100% 相同,因为你不能用 a 来表达一个键.;方括号不起作用 ( []);你可以用 javascript 不允许的方式表达键,例如a.b-c.dfor obj.a[b-c].d; 但这些都是很小的,如果有人真的想要的话,增强这种类型以至少支持括号的情况将是非常简单的。
这是演示它的游乐场链接!我稍微编辑了类型的名称,简化了一些类型并删除了不必要的使用any,尽管我仍然不明白ShiftUnion之前答案中的类型如何解决问题,所以我留下了它。
基本上,您采用 ford04 的答案,只需将需要的路径包装在PathToStringArray类型中即可。
type PathToStringArray<T extends string> = T extends `${infer Head}.${infer Tail}` ? [...PathToStringArray<Head>, ...PathToStringArray<Tail>] : [T]
// ford04's answer, then
type DeepRequiredWithPathsSyntax<T, P extends string> = DeepRequired<T, PathsToStringArray<P>>
Run Code Online (Sandbox Code Playgroud)
结果是您可以使用点分隔的语法来创建这些路径,而不是冗长的数组语法,如下所示:
type Foo = { a?: 2, b?: { c?: 3, d: 4 } }
type A = RequireKeysDeep<Foo, "a">; // {a: 2, b?: { c?: 3, d: 4 } }
type B = RequireKeysDeep<Foo, "b">; // {a?: 2, b: { c?: 3, d: 4 } }
type BC = RequireKeysDeep<Foo, "b.c">; // {a?: 2, b: { c: 3, d: 4 } }
type ABC = RequireKeysDeep<Foo, "a" | "b.c">; // {a: 2, b: { c: 3, d: 4 } }
Run Code Online (Sandbox Code Playgroud)
测试在 Playground 链接中进行。
| 归档时间: |
|
| 查看次数: |
1748 次 |
| 最近记录: |