请考虑以下内容...
type Boxed<T> = { value: T }
type Unboxed<T> = T extends Boxed<infer R>
? R
: T extends Record<any, any>
? { [P in keyof T]: Unboxed<T[P]> }
: T
function unbox(v: Record<any, any>): Unboxed {
}
unbox
是一个函数,该函数接受任何类型的对象并递归将所有装箱的对象拆箱。例如...
const output = unbox({ foo: { value: 'foo' }, bar: 'bar' })
output === { foo: 'foo', bar: 'bar' }
这很好用,除非其中一个属性是也与extends Record<any, any>
匹配的内置 unboxed 。例如,日期,正则表达式,映射,集合等。映射不会在内置对象上进行映射,而是继续在原型上进行。当接口匹配时,这会导致类型不匹配,但不会导致实际的内置类型。
例如,以下代码行...
const unboxed: { date: Date } = Unboxed<{ date: Date }>
...引发此错误...
Type '{ date: { toString: {}; toDateString: {}; toTimeString: {}; toLocaleString: {}; toLocaleDateString: {}; toLocaleTimeString: {}; valueOf: {}; getTime: {}; getFullYear: {}; getUTCFullYear: {}; getMonth: {}; ... 32 more ...; getVarDate: {}; }; }' is not assignable to type '{ date: Date; }'.\n Types of property 'date' are incompatible.\n Property '[Symbol.toPrimitive]' is missing in type '{ toString: {}; toDateString: {}; toTimeString: {}; toLocaleString: {}; toLocaleDateString: {}; toLocaleTimeString: {}; valueOf: {}; getTime: {}; getFullYear: {}; getUTCFullYear: {}; getMonth: {}; ... 32 more ...; getVarDate: {}; }' but required in type 'Date'."
如何使用仅对纯对象执行递归的递归条件映射类型?
答案 0 :(得分:0)
我不确定如何期望类型系统中“内置”对象类型和“普通”对象类型之间的区别,而类型系统没有这种概念。可能您可以构建一个大型的内置类型的硬编码联合,以检查并使Unboxed<T>
具有类似T extends Map<any, any> | Set<any> | Date | RegExp | ...
的子句。好吧。
另一种可行的方式(在输入任何生产代码之前,您需要进行大量测试)是这样的假设:假设我将Unboxed<T>
用于类型T
并且我得到了T
仍可分配给它的新类型。这可能意味着Unboxed<T>
实际上没有解包T
内任何地方的任何东西。如果是这样,那么我可能应该只使用T
而不是Unboxed<T>
给我的东西。因此,如果T extends Unboxed<T>
,则返回T
。这种检查可能会翻译成这样:
type Unboxed<T> =
T extends Boxed<infer R> ? R :
T extends Function ? T :
T extends object ? (
{ [P in keyof T]: Unboxed<T[P]> } extends infer O ? T extends O ? T : O : never) :
T
(我还决定了Function
兼容的T
值不应该尝试取消装箱)。因此,如果某某T
的{{1}}是Boxed<R>
,我们得到R
。如果R
是函数或原始类型,则得到T
。否则,如果T
是某种对象类型,我们将计算T
并将其分配给{[P in keyof T]: Unboxed<T[P]>}
以方便重用。最后,我们将O
与O
进行比较。如果T
是O
的较宽版本,请使用T
。否则,请使用T
。
给出以下O
界面:
Example
这是您拆箱后得到的东西:
interface Example {
str: string;
num: number;
box: Boxed<{ foo: string }>;
fun: () => { value: string };
dat: Date;
subProp: {
str: string;
dat: Date;
map: Map<string, number>;
set: Set<boolean>;
reg: RegExp;
box: Boxed<{ bar: number }>;
}
}
所有内置类型均不变,type UnboxedExample = Unboxed<Example>;
/*
type UnboxedExample = {
str: string;
num: number;
box: {
foo: string;
};
fun: () => {
value: string;
};
dat: Date;
subProp: {
str: string;
dat: Date;
map: Map<string, number>;
set: Set<boolean>;
reg: RegExp;
box: {
bar: number;
};
};
}
*/
函数值属性也不变(请注意,这意味着fun
的返回类型仍为fun
而不是{ {1}},因为我们没有将函数返回值拆箱,对吗?属性{value: string}
和string
也被取消了包装。
这可能是我最接近您要求的内容。同样,您需要在使用它之前对其进行彻底的测试。。。有些奇怪的情况在此定义中不能很好地发挥作用。对于具有某些box
的{{1}}特制属性的对象,假设subProp.box
暗示T extends Unboxed<T>
内没有任何要取消装箱的操作是错误的。像这样一个:
T
在这里,U & Boxed<U>
的{{1}}属性看起来像U
,所以interface What {
foo: {
bar: string,
value: { bar: string }
}
}
type Wait = Unboxed<What>;
// type Wait = What
的定义错误地决定应按原样返回foo
拆箱。您的代码中会有这种奇怪的类型吗?可能不会。但是,仍然需要提防。
好的,希望对您有所帮助或至少能给您一些思路。祝你好运!