是否可以合并两个通用对象类型的 prop ?我有一个类似的函数:
function foo<A extends object, B extends object>(a: A, b: B) {
return Object.assign({}, a, b);
}
我希望类型是A中不存在于B中的所有属性,以及B中的所有属性。
merge({a: 42}, {b: "foo", a: "bar"});
给出了一个相当奇怪的{a: number} & {b: string, a: string}
类型,虽然a
是一个字符串。实际返回值给出了正确的类型,但我不知道如何显式地写它。
6条答案
按热度按时间plicqrtu1#
更新TS4.1 +
最初的答案仍然有效(如果需要解释,应该阅读它),但是现在支持递归条件类型,我们可以将
merge()
写成variadic:你可以测试一下:
Playground代码链接
原始答案
由TypeScript标准库定义
Object.assign()
生成的交集类型是一个近似值,如果后面的参数具有与前面的参数同名的属性,则不能正确地表示所发生的情况。但是直到最近,这是在TypeScript的类型系统中所能做的最好的事情。然而,从TypeScript 2.8中引入条件类型开始,您可以使用更接近的近似值。其中一个改进是使用这里定义的类型函数
Spread<L,R>
,如下所示:(我稍微修改了相关的定义;使用来自标准库的
Exclude
而不是Diff
,并用无操作Id
类型 PackageSpread
类型以使所检查的类型比一堆交集更易处理)。我们来试试看:
您可以看到输出中的
a
现在被正确地识别为string
,而不是string & number
。但请注意,这仍然是一个近似值:
Object.assign()
只复制enumerable, own properties,并且类型系统不提供任何方式来表示要过滤的属性的可枚举性和所有权。这意味着merge({},new Date())
在TypeScript中看起来像类型Date
,即使在运行时,Date
方法都不会被复制,并且输出基本上是{}
。Spread
的定义在 * missing * 属性和 * present with a undefined value * 属性之间并不是真正的distinguish。因此,merge({ a: 42}, {a: undefined})
被错误地键入为{a: number}
,而它应该是{a: undefined}
。这可能可以通过重新定义Spread
来修复。但我不是100%肯定。而且对大多数用户来说可能没有必要。(编辑:这可以通过重新定义type OptionalPropertyNames<T> = { [K in keyof T]-?: ({} extends { [P in K]: T[K] } ? K : never) }[keyof T]
来解决)declare const whoKnows: {}; const notGreat = merge({a: 42}, whoKnows);
在编译时会有一个{a: number}
的输出类型,但是如果whoKnows
碰巧是{a: "bar"}
(它可以赋值给{}
),那么notGreat.a
在运行时是一个字符串,而在编译时是一个数字。所以请注意将
Object.assign()
键入为交集或Spread<>
是一种"尽力而为"的做法,在边缘情况下可能会使您误入歧途。Playground代码链接
Id<T>
是一个身份类型,原则上不应该对该类型做任何事情。有人在某个时候编辑了这个答案,将其删除,并只替换为T
。这样的更改并不完全是不正确的,但它违背了目的...这是通过迭代键来消除交集。如果你检查
IdFoo
,你会发现交集已经被消除了,两个成分已经合并成一个类型,同样,在可赋值性方面,Foo
和IdFoo
之间没有真正的区别;只是后者在某些情况下更容易读懂。4urapxun2#
我发现了一种语法,可以声明一个类型,该类型可以合并任意两个对象的所有属性。
此类型允许您指定任意两个对象A和B。
从这些对象中,创建一个Map类型,其键是从任一对象的可用键派生的。
然后,通过从源代码中查找相应的类型,将每个键Map到该键的类型。如果键来自
B
,则类型是来自B
的键的类型。这是通过K extends keyof B ?
完成的。这部分会问这样一个问题:“K
是来自B
的键吗?”要获得该键的类型,K
,使用属性查找B[K]
。如果密钥不是来自
B
,则它必须来自A
,因此三元组完成:x1米10英寸1x
所有这些都 Package 在对象符号
{ }
中,使其成为Map的对象类型,其键派生自两个对象,其类型Map到源类型。oalqel3c3#
如果要保留特性顺序,请使用以下解决方案。
在此观看实际应用。
u5rb5r594#
我认为您需要的是 union(
|
)类型,而不是交集(&
)类型。它更接近您想要的类型...学习TS我花了很多时间回到this page ...这是一本好书(如果你喜欢这类东西的话),提供了很多有用的信息
gdx19jrr5#
时间;日期
另一个答案的动机
jcalz answer很好,为我工作了很多年,不幸的是,随着合并对象的计数增长到一定数量,typescript产生了下面的错误:
并且无法推导出结果的对象类型。这是由于在下面的github issue中已经讨论过太多的类型脚本问题:https://github.com/microsoft/TypeScript/issues/34933
详情
在上面的
merge()
代码中,A[number]
类型扩展为数组元素类型的并集。UnionToIntersection
元函数将并集转换为交集。Expand
将交集展平,以便类似于IntelliSense的工具更容易读取。有关
UnionToIntersection
和Expand
实现的更多详细信息,请参见以下参考:https://stackoverflow.com/a/50375286/5000057
https://github.com/shian15810/type-expand
额外
在使用
merge()
函数时,合并对象中的键重复很可能是错误的,可以使用下面的函数来查找这样的重复和throw Error
:ruoxqz4g6#
我喜欢这个来自@Michael P. Scott的answer。
因为我也在寻找它,所以我做得简单一些。让我一步一步地分享和解释它。
1.使用
A
类型作为合并类型的基。1.获取
A
中没有的B
的键(Exclude这样的实用程序类型会有所帮助)。1.最后,求步骤
#1
和#2
中的类型与&
的交集,以获得组合类型。