有没有任何方法可以构建一个类似DeepRequired<T>
的帮助器类型,它只会影响不是"的键;外部";到T
?
我的意思是,使用这个示例代码:
interface Test{
prop1?: string
prop2: {
prop3?: {
prop3b?: HTMLElement
}
prop4?: number
}
}
declare const test: DeepRequired<Test>
const c: HTMLElement = test.prop2.prop3.prop3b
这将在c
上失败,错误为:
类型"{accessKey:string;只读accessKeyLabel:string;autocapitalize:string;dir:string,可拖动:布尔;隐藏:布尔;innerText:string,lang:string、只读offsetHeight:number;…233 more…;focus:(options?:FocusOptions|undefined(=>void;}"不可分配给类型"HTMLElement"。
如果我理解正确的话,这是因为prop3b?: HTMLElement
已经转换为prop3b: DeepRequired<HTMLElement>
。
有没有办法成为";"深而浅"?
我尝试过构建自己的递归类型,比如:
type IsObject<T> = T extends AnyArray
? false
: T extends object
? true
: false
type RRequired<T> = {
[P in keyof T]-?: NonNullable<T[P]> extends Builtin
? T[P]
: NonNullable<T[P]> extends AnyArray
? T[P]
: IsObject<NonNullable<T[P]>> extends true
? RRequired<T[P]>
: T[P]
}
但我找不到停止递归的方法。
我的理解是,对于TypeScript,我的Test
接口相当于:
interface Test{
prop1?: string
prop2: {
prop3?: {
prop3b?: {
...HTMLElement
}
}
prop4?: number
}
}
即,在我使用HTMLElement
的类型中内联的所有属性(或任何其他类型/接口(。。。所以我不知道如何区分我的接口的嵌套属性和";"外部";,非内置类型。
那么,有没有什么方法可以编写一个RRequired<T>
类型,将Test
转换为以下类型?
RRequired<Test> == {
prop1: string
prop2: {
prop3: {
prop3b: HTMLElement // <- preserve HTMLElement
}
prop4: number
}
}
实现这样的目标的唯一方法是对照;允许的类型";你不想让它被深深地要求。例如
type DeepRequired<T> = T extends HTMLElement ? T : {
[K in keyof T]-?: DeepRequired<T[K]>
}
或
type AllowedTypes = HTMLElement | WhateverTypeYouWantToAllow | ...
type DeepRequired<T> = T extends AllowedTypes ? T : {
[K in keyof T]-?: DeepRequired<T[K]>
}
但我们知道,这会检查结构是否匹配。这意味着,如果你有一个形状与HTMLElement
完全相似的类型,或者你的AllowedTypes
中的任何元素都是类似形状的,那么这个条件就会短路,不会给你想要的结果。这里类型Bogus
类似于Test.prop2
中的所有内容,这意味着prop2
以下的所有内容都不需要
type Bogus = {
prop4?: number;
}
type AllowedTypes = HTMLElement | Bogus;
type DeepRequired<T> = T extends AllowedTypes ? T : {
[K in keyof T]-?: DeepRequired<T[K]>
}
declare const z: DeepRequired<Test>
z.prop2.prop3.prop3b // BOOM, prop3 is optional!
由于HTMLElement及其所有子类型都是非常复杂的形状,您很可能会使用这种方法。但在一个结构类型的系统中,形状就是一切,随之而来的是一些棘手的问题。