Typescript:使用方括号访问接口子类型..可选属性的问题


interface Body {
size: number;
meta?: Meta;
}
export interface Request {
body?: Body;
}

我认为这会起作用:

type MyMeta = Request["body"]["meta"];

然而,因为body是可选的,所以Request["body"]的类型是Body | undefined,所以我得到了

Property 'meta' does not exist on type 'Body | undefined'.ts(2339)

除了之外,还有其他方法吗

type MyMeta = Exclude<Request["body"], undefined>["meta"];
type MyMeta = Required<Request>['body']['meta'];

对于更深层的房产来说,这将是个问题。

使用DeepRequired泛型也是个坏主意,现在meta中的每个属性都是必需的:

export type DeepRequired<T> = Required<{
[K in keyof T]: DeepRequired<T[K]>
}>
type MyMeta =  DeepRequired<Request>['body']['meta'];

您可以创建一个递归类型Index,该类型接受一个键数组并遍历对象类型,同时放弃undefined备选方案:

type Index<P, T> = 
P extends readonly [infer Key, ...infer Rest]
? T extends undefined ? never : Index<Rest, T[Extract<Key, keyof T>]>
: T 

因为Index不约束路径类型,所以它只会为不正确的路径生成never。这可以通过声明一个Shape类型来弥补,该类型使用每个路径键的可选键(从我对另一个问题的回答中窃取https://stackoverflow.com/a/67351133/5770132,请参阅该答案以了解更多详细信息(。

type Shape<P> =
P extends readonly [infer Key, ...infer Rest]
? {[K in Extract<Key, PropertyKey>]?: Shape<Rest>}
: unknown 

使用Shape,我们可以声明一个在不正确的路径上给出错误的DeepIndex

type DeepIndex<T extends Shape<Path>, Path extends readonly PropertyKey[]> = Index<Path, T>

几个例子(添加了Meta的定义(:

interface Meta { x: 42, meta: Meta }
interface Body { size: number; meta?: Meta }
export interface Request { body?: Body }
{ type Test = DeepIndex<Request, ['body']> }
// type Test = Body | undefined
{ type Test = DeepIndex<Request, ['body', 'size']> }
// type Test = number
{ type Test = DeepIndex<Request, ['body', 'meta', 'x']> }
// type Test = 42
{ type Test = DeepIndex<Request, ['body', 'meta', 'meta', 'meta', 'x']> }
// type Test = 42
{ type Test = DeepIndex<Request, ['nobody', 'size']> }
// ERROR: Type 'Request' has no properties in common with type
//        '{ nobody?: { size?: unknown; } | undefined; }

请注意,如果最后一个属性是可选的,则结果包括undefined,但如果前面的任何属性都是可选的则不会自动这样做。在这条道路上传播选择性是可能的,但会使事情变得相当复杂。从结果中删除undefined也是可能的,但这将为碰巧具有undefined类型的属性返回never(但可能并不常见(。

TypeScript游乐场

您可以使用内置的NonNullable类型,它只是Exclude<T, undefined | null>:的别名

interface Body {
size: number;
meta?: { __notSpecified: never };
}
export interface Request {
body?: Body;
}
type MyMeta = NonNullable<Request["body"]>["meta"]; // { __notSpecified: never; } | undefined

TypeScript游乐场链接

除此之外,你真的没有别的办法了

最新更新