打字稿:在 "Deep" 对象类型中省略对象 "Levels"
Typescript: Omitting Object "Levels" in "Deep" Object Types
鉴于 API 可以提供对象图的 "expanded" 版本,我希望获得一种定义类型的简单方法 returned.
例如,对于这些定义
interface A {
id: string;
q: string;
b: B;
cs: C[];
}
interface B {
id: string;
prev: B;
c: C;
}
interface C {
id: string;
foo: string;
ds: D[];
}
interface D {
id: string;
e: E;
}
interface E {
id: string;
}
我需要得出例如以下类型
type A0 = {
id: string,
q: string,
b: { id: string },
cs: Array<{id: string}>,
};
type A1 = {
id: string,
q: string,
b: {
id: string,
prev: { id: string },
c: { id: string },
},
cs: Array<{
id: string,
foo: string,
ds: Array<{id: string}>,
}>,
};
type A2 = {
id: string,
q: string,
b: {
id: string,
prev: {
id: string,
prev: { id: string },
c: { id: string },
},
c: {
id: string,
foo: string,
ds: Array<{
id: string,
}>,
},
},
cs: Array<{
id: string,
foo: string,
ds: Array<{
id: string,
e: { id: string},
}>,
}>,
};
我拥有的是以下类型(以此类推更大的级别)
export type Retain0Levels<T> = {
[P in keyof T]:
T[P] extends infer TP
? TP extends Primitive
? TP
: TP extends any[]
? Array<{ id: string }>
: { id: string }
: never
};
export type Retain1Level<T> = {
[P in keyof T]:
T[P] extends infer TP
? TP extends Primitive
? TP
: TP extends any[]
? { [I in keyof TP]: Retain0Levels<TP[I]> }
: Retain0Levels<TP>
: never
};
export type Retain2Levels<T> = {
[P in keyof T]:
T[P] extends infer TP
? TP extends Primitive
? TP
: TP extends any[]
? { [I in keyof TP]: Retain1Level<TP[I]> }
: Retain1Level<TP>
: never
};
type Primitive = string | Function | number | boolean | symbol | undefined | null;
这很好用,但当然有两个问题:
- 我必须为每个级别定义一个类型。如果只有两种类型(我假设一种不起作用或会变得非常混乱)会很好:叶子(level0)和递归定义的更高阶级别。
- 我的 API 访问处理程序的使用者必须将 return 类型(例如
A
)转换为他们请求的类型(通过提供一个参数来指示要扩展的级别).根据提供的输入参数设置 return 类型会很好。
作为 (2) 的示例,假设我们有一个调用后端 API 的函数,例如getAs(levels: number): A[]
。我希望这是 getAs(levels: number): Array<RetainLevels<A, levels>>
(或类似的)。如果不是,消费者必须调用该方法,例如getAs(5) as RetainLevels<A, 5>
.
有没有人对如何改进我目前必须解决的(其中一个)上述问题有任何建议?
P.S。感谢 @titian-cernicova-dragomir
从谁的 我推导出我目前拥有的
已编辑
更正了@jcalz 指出的错误,并根据定义的类型为 "dynamic return type" 用例添加了示例。
为了减少类型定义的重复性,我建议这样:
type PrevNum = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];
// lengthen as needed
export type RetainLevel<N extends number, T> = {
[P in keyof T]: T[P] extends infer TP
? TP extends Primitive
? TP
: TP extends any[] ? { [I in keyof TP]: Rec<N, TP[I]> } : Rec<N, TP>
: never
};
type Rec<N extends number, T> = N extends 0
? { id: string }
: RetainLevel<PrevNum[N], T>;
这允许您指定类型,例如 RetainLevel<10, XXX>
而不是 Retain10Level<XXX>
。 TypeScript 并不能真正让您进行类型级算术运算,因此无法从任意数字中减去 1。不过 PrevNum
元组可能已经足够好了;只要你认为人们会要求的最大合理价值,你就可以做到。
正在测试这是否给出了您指定的类型:
type MutuallyExtends<T extends U, U extends V, V = T> = true;
type A0 = RetainLevel<0, A>;
type A0Okay = MutuallyExtends<A0, A0Manual>; // okay
type A1 = RetainLevel<1, A>;
type A1Okay = MutuallyExtends<A1, A1Manual>; // okay
这些看起来不错。
type A2 = RetainLevel<2, A>;
type A2Okay = MutuallyExtends<A2, A2Manual>; // error?
您的 A2
类型似乎与 RetainLevel<2, A>
不同。虽然我检查过你的 Retain2Levels<A>
与 RetainLevel<2, A>
相同,所以我猜你的代码并不完全构成 minimum reproducible example。我假设没关系。
关于铸造部分,请在代码中举例说明你的意思,然后也许我或其他人能够回答这个问题。如果您的意思是将数字作为参数传入,那么如果您在 N
中使函数通用,则上述内容可能会有所帮助,即该数字的类型。似乎是一个单独的问题,也许吧,但你是老板!
UPDATE:看到你的例子 getAs()
,是的,现在 RetainLevel
在数字 N
中是通用的,你可以getAs()
也通用:
declare function getAs<N extends number>(levels: N): Array<RetainLevel<N, A>>;
这应该按照您想要的方式工作:
const a0s = getAs(0); // Array<RetainLevel<0, A>>, same as Array<A0>
const a1s = getAs(1); // Array<RetainLevel<1, A>>, same as Array<A1>
const a10s = getAs(10); // Array<RetainLevel<10, A>>;
好的,希望对您有所帮助;祝你好运!
鉴于 API 可以提供对象图的 "expanded" 版本,我希望获得一种定义类型的简单方法 returned.
例如,对于这些定义
interface A {
id: string;
q: string;
b: B;
cs: C[];
}
interface B {
id: string;
prev: B;
c: C;
}
interface C {
id: string;
foo: string;
ds: D[];
}
interface D {
id: string;
e: E;
}
interface E {
id: string;
}
我需要得出例如以下类型
type A0 = {
id: string,
q: string,
b: { id: string },
cs: Array<{id: string}>,
};
type A1 = {
id: string,
q: string,
b: {
id: string,
prev: { id: string },
c: { id: string },
},
cs: Array<{
id: string,
foo: string,
ds: Array<{id: string}>,
}>,
};
type A2 = {
id: string,
q: string,
b: {
id: string,
prev: {
id: string,
prev: { id: string },
c: { id: string },
},
c: {
id: string,
foo: string,
ds: Array<{
id: string,
}>,
},
},
cs: Array<{
id: string,
foo: string,
ds: Array<{
id: string,
e: { id: string},
}>,
}>,
};
我拥有的是以下类型(以此类推更大的级别)
export type Retain0Levels<T> = {
[P in keyof T]:
T[P] extends infer TP
? TP extends Primitive
? TP
: TP extends any[]
? Array<{ id: string }>
: { id: string }
: never
};
export type Retain1Level<T> = {
[P in keyof T]:
T[P] extends infer TP
? TP extends Primitive
? TP
: TP extends any[]
? { [I in keyof TP]: Retain0Levels<TP[I]> }
: Retain0Levels<TP>
: never
};
export type Retain2Levels<T> = {
[P in keyof T]:
T[P] extends infer TP
? TP extends Primitive
? TP
: TP extends any[]
? { [I in keyof TP]: Retain1Level<TP[I]> }
: Retain1Level<TP>
: never
};
type Primitive = string | Function | number | boolean | symbol | undefined | null;
这很好用,但当然有两个问题:
- 我必须为每个级别定义一个类型。如果只有两种类型(我假设一种不起作用或会变得非常混乱)会很好:叶子(level0)和递归定义的更高阶级别。
- 我的 API 访问处理程序的使用者必须将 return 类型(例如
A
)转换为他们请求的类型(通过提供一个参数来指示要扩展的级别).根据提供的输入参数设置 return 类型会很好。
作为 (2) 的示例,假设我们有一个调用后端 API 的函数,例如getAs(levels: number): A[]
。我希望这是 getAs(levels: number): Array<RetainLevels<A, levels>>
(或类似的)。如果不是,消费者必须调用该方法,例如getAs(5) as RetainLevels<A, 5>
.
有没有人对如何改进我目前必须解决的(其中一个)上述问题有任何建议?
P.S。感谢 @titian-cernicova-dragomir
从谁的
已编辑
更正了@jcalz 指出的错误,并根据定义的类型为 "dynamic return type" 用例添加了示例。
为了减少类型定义的重复性,我建议这样:
type PrevNum = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];
// lengthen as needed
export type RetainLevel<N extends number, T> = {
[P in keyof T]: T[P] extends infer TP
? TP extends Primitive
? TP
: TP extends any[] ? { [I in keyof TP]: Rec<N, TP[I]> } : Rec<N, TP>
: never
};
type Rec<N extends number, T> = N extends 0
? { id: string }
: RetainLevel<PrevNum[N], T>;
这允许您指定类型,例如 RetainLevel<10, XXX>
而不是 Retain10Level<XXX>
。 TypeScript 并不能真正让您进行类型级算术运算,因此无法从任意数字中减去 1。不过 PrevNum
元组可能已经足够好了;只要你认为人们会要求的最大合理价值,你就可以做到。
正在测试这是否给出了您指定的类型:
type MutuallyExtends<T extends U, U extends V, V = T> = true;
type A0 = RetainLevel<0, A>;
type A0Okay = MutuallyExtends<A0, A0Manual>; // okay
type A1 = RetainLevel<1, A>;
type A1Okay = MutuallyExtends<A1, A1Manual>; // okay
这些看起来不错。
type A2 = RetainLevel<2, A>;
type A2Okay = MutuallyExtends<A2, A2Manual>; // error?
您的 A2
类型似乎与 RetainLevel<2, A>
不同。虽然我检查过你的 Retain2Levels<A>
与 RetainLevel<2, A>
相同,所以我猜你的代码并不完全构成 minimum reproducible example。我假设没关系。
关于铸造部分,请在代码中举例说明你的意思,然后也许我或其他人能够回答这个问题。如果您的意思是将数字作为参数传入,那么如果您在 N
中使函数通用,则上述内容可能会有所帮助,即该数字的类型。似乎是一个单独的问题,也许吧,但你是老板!
UPDATE:看到你的例子 getAs()
,是的,现在 RetainLevel
在数字 N
中是通用的,你可以getAs()
也通用:
declare function getAs<N extends number>(levels: N): Array<RetainLevel<N, A>>;
这应该按照您想要的方式工作:
const a0s = getAs(0); // Array<RetainLevel<0, A>>, same as Array<A0>
const a1s = getAs(1); // Array<RetainLevel<1, A>>, same as Array<A1>
const a10s = getAs(10); // Array<RetainLevel<10, A>>;
好的,希望对您有所帮助;祝你好运!