Jom*_*mik 10 generics merge types object typescript
是否可以合并两个通用对象类型的道具?我有一个类似这样的功能:
function foo<A extends object, B extends object>(a: A, b: B) {
return Object.assign({}, a, b);
}
Run Code Online (Sandbox Code Playgroud)
我希望该类型是A中B中不存在的所有属性,以及B中的所有属性.
merge({a: 42}, {b: "foo", a: "bar"});
Run Code Online (Sandbox Code Playgroud)
给出一个相当奇怪的类型{a: number} & {b: string, a: string},a虽然是一个字符串.实际的返回给出了正确的类型,但我无法想象我将如何明确地写它.
jca*_*alz 17
由TypeScript标准库定义Object.assign()生成的交集类型是一种近似值,如果后面的参数具有与先前参数同名的属性,则该近似值无法正确表示会发生什么.但直到最近,这才是TypeScript类型系统中最好的.
然而,从在TypeScript 2.8中引入条件类型开始,您可以使用更接近的近似值.一个这样的改进是使用这里Spread<L,R>定义的类型函数,如下所示:
// Names of properties in T with types that include undefined
type OptionalPropertyNames<T> =
{ [K in keyof T]: undefined extends T[K] ? K : never }[keyof T];
// Common properties from L and R with undefined in R[K] replaced by type in L[K]
type SpreadProperties<L, R, K extends keyof L & keyof R> =
{ [P in K]: L[P] | Exclude<R[P], undefined> };
type Id<T> = {[K in keyof T]: T[K]} // see note at bottom*
// Type of { ...L, ...R }
type Spread<L, R> = Id<
// Properties in L that don't exist in R
& Pick<L, Exclude<keyof L, keyof R>>
// Properties in R with types that exclude undefined
& Pick<R, Exclude<keyof R, OptionalPropertyNames<R>>>
// Properties in R, with types that include undefined, that don't exist in L
& Pick<R, Exclude<OptionalPropertyNames<R>, keyof L>>
// Properties in R, with types that include undefined, that exist in L
& SpreadProperties<L, R, OptionalPropertyNames<R> & keyof L>
>;
Run Code Online (Sandbox Code Playgroud)
(我稍微更改了链接定义;使用Exclude标准库代替Diff,并Spread使用no-op Id类型包装类型以使检查类型比一堆交叉点更容易处理).
我们来试试吧:
function merge<A extends object, B extends object>(a: A, b: B) {
return Object.assign({}, a, b) as Spread<A, B>;
}
const merged = merge({ a: 42 }, { b: "foo", a: "bar" });
// {a: string; b: string;} as desired
Run Code Online (Sandbox Code Playgroud)
您可以看到a输出现在已正确识别为string而不是string & number.好极了!
但请注意,这仍然是一个近似值:
Object.assign()只复制可枚举,自己的属性和类型系统不会给你任何方式来表示要过滤的属性的可枚举性和所有权.merge({},new Date())看起来类似于DateTypeScript的含义,即使在运行时Date也不会复制任何方法,并且输出本质上是{}.这是现在的硬限制.
此外,定义Spread并没有真正区分之间缺少属性和是一个属性本与一个未定义的值.因此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<>是一种"尽力而为"的东西,并且可以在边缘情况下引导你误入歧途.
无论如何,希望有所帮助.祝好运!
*注意:有人编辑Id<T>了身份映射类型的定义T.这样的改变确切地说并不是错误的,但它违背了目的......即迭代键以消除交叉点.相比:
type Id<T> = { [K in keyof T]: T[K] }
type Foo = { a: string } & { b: number };
type IdFoo = Id<Foo>; // {a: string, b: number }
Run Code Online (Sandbox Code Playgroud)
如果您检查,IdFoo您将看到交叉点已被消除,并且两个成分已合并为单一类型.再次,有没有真正的区别Foo,并IdFoo在可转让的条款; 只是后者在某些情况下更容易阅读.不可否认,有时编译器的类型的字符串表示只是不透明的Id<Foo>,所以它并不完美.但它确实有目的.如果您想在自己的代码中替换Id<T>,T请成为我的客人.
Mic*_*ott 14
感谢re-gor的评论,我重新审视了这一点并更新了语法以更加明确地了解合并。
type Merge<A, B> = {
[K in keyof A | keyof B]:
K extends keyof A & keyof B
? A[K] | B[K]
: K extends keyof B
? B[K]
: K extends keyof A
? A[K]
: never;
};
Run Code Online (Sandbox Code Playgroud)
通过将keyof操作符单独扩展为A和B,所有的键都被暴露出来。
使用嵌套三元类型,首先检查键是否存在于A和中B,如果存在,则类型为A[K] | B[K]。
接下来,当密钥仅来自 时B,则类型为B[K]。
接下来,当密钥仅来自 时A,则类型为A[K]。
最后,键既不存在,也不A存在B,类型为never。
type Merge<A, B> = {
[K in keyof A | keyof B]:
K extends keyof A & keyof B
? A[K] | B[K]
: K extends keyof B
? B[K]
: K extends keyof A
? A[K]
: never;
};
Run Code Online (Sandbox Code Playgroud)
我找到了一种声明合并任意两个对象的所有属性的类型的语法。
type Merge<A, B> = { [K in keyof (A | B)]: K extends keyof B ? B[K] : A[K] };
Run Code Online (Sandbox Code Playgroud)
此类型允许您指定任意两个对象 A 和 B。
由此,创建一个映射类型,其键源自任一对象的可用键。钥匙来自keyof (A | B).
然后,通过从源中查找适当的类型,将每个键映射到该键的类型。如果密钥来自B,则类型是该密钥来自 的类型B。这是用 完成的K extends keyof B ?。这部分提出了一个问题:“K钥匙来自B”?要获取该键的类型,K请使用属性查找B[K]。
如果键不是 from B,则它必须是 from A,这样三元就完成了:
K extends keyof B ? B[K] : A[K]
所有这些都包装在对象符号中{ },使其成为映射对象类型,其键派生自两个对象,其类型映射到源类型。