我想编写一个函数,用另一个对象扩展一个对象。例如:我们将调用一个对象src
,再调用另一个对象ext
。该函数必须返回(复制)src
对象,但是用对象将对象深度(递归)扩展ext
。如果的一个(子)属性的数据类型与(子)属性ext
的类型不匹配,则src
该函数必须忽略该ext
值。中的新属性ext
(不存在src
)将添加到结果对象中。
为了更好地理解这里的完整示例:
/** A helper definition to build indexable objects */
interface indexObject<T> {
[key: string]: T
}
type SafelyMergedObject<Src extends indexObject<any>, Ext extends indexObject<any>> = {
// The searched type definition
}
const src= {
a: "a",
b: "b",
c: false,
d: {
a: "Alice",
b: "Bob",
}
}
const ext = {
a: ["a"],
b: 1,
c: true,
d: {
a: "Ann",
c: "Clair",
},
e: "New value",
}
const result: SafelyMergedObject<typeof src, typeof ext> = {
a: "a", /** Only string should be allowed here because
it's the property type of the source (Src) type */
b: "b", /** Same as above (`a` property) */
c: true,/** Since `c` has the same data type the function
should return the property value of the `ext`
object */
d: { /** Same data type in both objects */
a: "Ann", /** new value from `ext` */
b: "Bob", /** copied value from `src` */
c: "Clair", /** new property from `ext` */
},
e: "New Value", /** new property from `ext` */
}
我知道如何编写函数。这很容易,但是我不知道如何编写此类型定义。那有可能吗?
默认的TypeScript类型推断行为不适合我的问题,因为类型是递归的,并且比简单object
类型更复杂。例如,我将使用该功能将用户特定的配置加载到我的应用中。用户配置可能已损坏。因此,我必须将默认配置与用户特定的配置合并。
我将按以下方式解释您的要求:对于对象类型T
和U
,对象类型SafelyMergedObject<T, U>
应具有与相同的键T & U
,但属性类型有所不同。如果某个键同时K
存在T
或U
不存在,则按原样使用属性(因此与相同T & U
)。如果键K
既存在又存在,T
并且U
两个属性类型中的至少一个不是对象,则使用from中的属性类型,T
而忽略from中的属性U
。如果密钥K
中既存在T
和U
和是一个对象类型在两个T
和U
,然后递归分解成经由该属性SafelyMergedObject<T[K], U[K]>
。
这被翻译成类似:
type SafelyMergedObject<T, U> = (
Omit<U, keyof T> & { [K in keyof T]:
K extends keyof U ? (
[U[K], T[K]] extends [object, object] ?
SafelyMergedObject<T[K], U[K]>
: T[K]
) : T[K] }
) extends infer O ? { [K in keyof O]: O[K] } : never;
在这里我们首先输出Omit<U, keyof T>
,这是U中不存在的属性T
。然后,我们将遍历的键T
,并输出T[K]
该属性是否不在中U
,或者如果该属性在其中,U
但至少是一个T[K]
或U[K]
不是一个对象,则输出。
这里唯一的“把戏”是extends infer O ? {[K in keyof O]: O[K]} : never
。所有这些都是通过迭代所有键并将结果合并为单个对象类型来“修饰”或“扩展”对象类型。
让我们看看您的src
和ext
值的作用:
type Result = SafelyMergedObject<typeof src, typeof ext>;
如果您将鼠标悬停在IntelliSense上,则会看到:
type Result = {
e: string;
a: string;
b: string;
c: boolean;
d: {
c: string;
a: string;
b: string;
};
}
我想这就是您想要的。请注意,如果我不包括该extends infer O...
行,则该Result
类型将被评估为:
type Result = Pick<{
a: string[];
b: number;
c: boolean;
d: {
a: string;
c: string;
};
e: string;
}, "e"> & {
a: string;
b: string;
c: boolean;
d: SafelyMergedObject<{
a: string;
b: string;
}, {
a: string;
c: string;
}>;
}
尽管它属于同一类型,但很难理解。
请注意,如果您SafelyMergedObject<T, U>
在不同情况下使用上述方法,可能会出现各种边缘情况。您需要确定在这些情况下输出的外观,并可能需要调整定义才能使输出变为现实。所以要小心
好的,希望能有所帮助;祝好运!
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句