打字稿,合并对象类型? [英] Typescript, merge object types?

查看:81
本文介绍了打字稿,合并对象类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否可以合并两种通用对象类型的道具? 我有类似的功能:

Is it possible to merge the props of two generic object types? I have a function similar to this:

function foo<A extends object, B extends object>(a: A, b: B) {
    return Object.assign({}, a, b);
}

我希望类型是A中B中不存在的所有属性,以及B中所有的属性.

I would like the type to be all the properties in A that does not exist in B, and all properties in B.

merge({a: 42}, {b: "foo", a: "bar"});

给出了{a: number} & {b: string, a: string}的一种很奇怪的类型,尽管a是一个字符串. 实际的返回值给出了正确的类型,但是我不知道如何显式编写它.

gives a rather odd type of {a: number} & {b: string, a: string}, a is a string though. The actual return gives the correct type, but I can not figure how I would explicitly write it.

推荐答案

The intersection type produced by the TypeScript standard library definition of Object.assign() is an approximation that doesn't properly represent what happens if a later argument has a property with the same name as an earlier argument. Until very recently, though, this was the best you could do in TypeScript's type system.

从引入条件类型但是,在TypeScript 2.8中,您可以找到更接近的近似值.此类改进之一是使用在此处定义的类型函数Spread<L,R>,像这样:

Starting with the introduction of conditional types in TypeScript 2.8, however, there are closer approximations available to you. One such improvement is to use the type function Spread<L,R> defined here, like this:

// 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>
  >;

(我对链接定义进行了一些更改;使用标准库中的Exclude而不是Diff,并将Spread类型包装为no-op Id类型,以使被检查的类型更易于处理而不是一堆交叉路口.

(I've changed the linked definitions slightly; using Exclude from the standard library instead of Diff, and wrapping the Spread type with the no-op Id type to make the inspected type more tractable than a bunch of intersections).

让我们尝试一下:

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

您可以看到输出中的a现在已被正确识别为string而不是string & number.是的!

You can see that a in the output is now correctly recognized as a string instead of string & number. Yay!

但是请注意,这仍然是一个近似值:

But note that this is still an approximation:

  • Object.assign()仅复制可枚举的自有财产,并且类型系统不提供任何方式来表示要过滤的属性的可枚举性和所有权.这意味着merge({},new Date())看起来就像TypeScript的Date类型,即使在运行时没有任何Date方法将被复制并且输出实质上是{}.这是目前的硬限制.

  • Object.assign() only copies enumerable, own properties, and the type system doesn't give you any way to represent the enumerability and ownership of a property to filter on. Meaning that merge({},new Date()) will look like type Date to TypeScript, even though at runtime none of the Date methods will be copied over and the output is essentially {}. This is a hard limit for now.

此外,Spread的定义实际上不是区别 missing 属性和存在未定义值的属性之间.因此,当merge({ a: 42}, {a: undefined})应该为{a: undefined}时,错误地将其键入为{a: number}.可以通过重新定义Spread来解决此问题,但我不确定100%.对于大多数用户而言,它可能不是必需的. (可以通过重新定义type OptionalPropertyNames<T> = { [K in keyof T]-?: ({} extends { [P in K]: T[K] } ? K : never) }[keyof T]来解决)

Additionally, the definition of Spread doesn't really distinguish between missing properties and a property that is present with an undefined value. So merge({ a: 42}, {a: undefined}) is erroneously typed as {a: number} when it should be {a: undefined}. This can probably be fixed by redefining Spread, but I'm not 100% sure. And it might not be necessary for most users. ( this can be fixed by redefining 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在运行时为字符串,但为数字编译时间.哎呀.

The type system can't do anything with properties it doesn't know about. declare const whoKnows: {}; const notGreat = merge({a: 42}, whoKnows); will have an output type of {a: number} at compile time, but if whoKnows happens to be {a: "bar"} (which is assignable to {}), then notGreat.a is a string at runtime but a number at compile time. Oops.

因此被警告;将Object.assign()键入为相交或Spread<>是一种尽力而为"的事情,在边缘情况下可能会使您误入歧途.

So be warned; the typing of Object.assign() as an intersection or a Spread<> is kind of a "best-effort" thing, and can lead you astray in edge cases.

无论如何,希望能有所帮助.祝你好运!

Anyway, hope that helps. Good luck!

*注:有人从身份映射类型将Id<T>的定义编辑为T.确切地说,这样的更改是不正确的,但是它违背了目的,即迭代键以消除交集.比较:

*Note: Someone edited the definition of Id<T> from a identity mapped type to be just T. Such a change isn't incorrect, exactly, but it defeats the purpose... which is to iterate through the keys to eliminate intersections. Compare:

type Id<T> = { [K in keyof T]: T[K] }

type Foo = { a: string } & { b: number };
type IdFoo = Id<Foo>; // {a: string, b: number }

如果检查IdFoo,您将看到相交已被消除,并且两个成分已合并为一种类型.同样,在可分配性方面,FooIdFoo之间没有真正的区别.只是后者在某些情况下更易于阅读.诚然,有时编译器的类型的字符串表示形式将只是不透明的Id<Foo>,因此并不完美.但这确实有目的.如果要在自己的代码中用T替换Id<T>,请成为我的客人.

If you inspect IdFoo you will see that the intersection has been eliminated and the two constituents have been merged into a single type. Again, there's no real difference between Foo and IdFoo in terms of assignability; it's just that the latter is easier to read in some circumstances. Admittedly, sometimes the compiler's string representation of a type will just be the opaque-ish Id<Foo>, so it's not perfect. But it did have a purpose. If you want to replace Id<T> with T in your own code, be my guest.

这篇关于打字稿,合并对象类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆