Typescript 类型脚本,合并对象类型?

Typescript 类型脚本,合并对象类型?,typescript,object,generics,types,merge,Typescript,Object,Generics,Types,Merge,是否可以合并两种通用对象类型的道具? 我有一个类似的功能: function foo<A extends object, B extends object>(a: A, b: B) { return Object.assign({}, a, b); } 给出了一个相当奇怪的{a:number}&{b:string,a:string},a是一个字符串。 实际返回给出了正确的类型,但我不知道如何显式地编写它。我认为您正在寻找更多的联合(|)类型,而不是交叉(&)类型。它更接近你

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

function foo<A extends object, B extends object>(a: A, b: B) {
    return Object.assign({}, a, b);
}
给出了一个相当奇怪的
{a:number}&{b:string,a:string}
a
是一个字符串。
实际返回给出了正确的类型,但我不知道如何显式地编写它。

我认为您正在寻找更多的联合(
|
)类型,而不是交叉(
&
)类型。它更接近你想要的

function merge<A, B>(a: A, b: B): A | B {
  return Object.assign({}, a, b)
}

merge({ a: "string" }, { a: 1 }).a // string | number
merge({ a: "string" }, { a: "1" }).a // string
函数合并。。。这是一本很好的读物(如果你对这类事情感兴趣的话),并且提供了很多有用的信息

TS4.1的更新+ 原始答案仍然有效(如果您需要解释,您应该阅读它),但是现在支持了,我们可以编写
merge()
,使用to be variadic:

type OptionalPropertyNames<T> =
  { [K in keyof T]-?: ({} extends { [P in K]: T[K] } ? K : never) }[keyof T];

type SpreadProperties<L, R, K extends keyof L & keyof R> =
  { [P in K]: L[P] | Exclude<R[P], undefined> };

type Id<T> = T extends infer U ? { [K in keyof U]: U[K] } : never

type SpreadTwo<L, R> = Id<
  & Pick<L, Exclude<keyof L, keyof R>>
  & Pick<R, Exclude<keyof R, OptionalPropertyNames<R>>>
  & Pick<R, Exclude<OptionalPropertyNames<R>, keyof L>>
  & SpreadProperties<L, R, OptionalPropertyNames<R> & keyof L>
>;

type Spread<A extends readonly [...any]> = A extends [infer L, ...infer R] ?
  SpreadTwo<L, Spread<R>> : unknown

type Foo = Spread<[{ a: string }, { a?: number }]>

function merge<A extends object[]>(...a: [...A]) {
  return Object.assign({}, ...a) as Spread<A>;
}
原始答案
由生成的交集类型不是正确表示如果后面的参数具有与前面的参数同名的属性会发生什么情况的交集类型。不过,直到最近,这还是在TypeScript的类型系统中所能做到的最好的

但是,从TypeScript 2.8中引入的开始,您可以使用更接近的近似值。其中一个改进是使用定义的type函数
Spread
,如下所示:

// 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>
  >;
您可以看到,输出中的
a
现在被正确地识别为
string
,而不是
string&number
。耶


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

  • Object.assign()
    仅复制,类型系统不会提供任何方式来表示要筛选的属性的可枚举性和所有权。这意味着
    merge({},new Date())
    对于TypeScript来说就像type
    Date
    ,即使在运行时没有任何
    Date
    方法会被复制,并且输出本质上是
    {}
    。目前这是一个硬限制

  • 此外,
    Spread
    的定义实际上并不区分缺少的属性和存在未定义值的属性。因此,
    merge({a:42},{a:undefined})
    被错误地键入为
    {a:number}
    ,而它应该是
    {a:undefined}
    。这可能可以通过重新定义
    排列来解决,但我不是100%确定。对于大多数用户来说,这可能不是必需的。(编辑:这可以通过重新定义
    类型OptionalPropertyNames={[K in keyof T]-?:({}扩展{[P in K]:T[K]}?K:never)}[keyof T]
    )来解决

  • 类型系统无法对其不知道的属性执行任何操作<代码>声明常量whowknows:{};const notGreat=merge({a:42},谁知道呢)
在编译时的输出类型为
{a:number}
,但如果
谁知道
恰好是
{a:“bar”}
(可分配给
{}
),则
不太好。a
在运行时是一个字符串,但在编译时是一个数字。哎呀

因此,请注意;键入
Object.assign()
作为交叉点或
Spread
是一种“尽力而为”的做法,在边缘情况下可能会使您误入歧途


*注意:有人将
Id
的定义从身份映射类型编辑为
T
。这样的改变并不是不正确的,但它违背了目的。。。这是通过迭代关键点来消除交叉点。比较:

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

type Foo = { a: string } & { b: number };
type IdFoo = Id<Foo>; // {a: string, b: number }
type Id={[K in keyof T]:T[K]}
类型Foo={a:string}&{b:number};
类型IdFoo=Id;//{a:string,b:number}

如果检查
IdFoo
,您将看到交叉点已被消除,两个组成部分已合并为一种类型。同样,在可分配性方面,
Foo
IdFoo
之间没有真正的区别;只是后者在某些情况下更容易阅读。诚然,有时编译器对类型的字符串表示将只是不透明的ish
Id
,因此它并不完美。但它确实有目的。如果您想在自己的代码中将
Id
替换为
T
,请随意。

如果您想保留财产顺序,请使用以下解决方案

看到它在行动

导出类型排列=Id<
//将L和R的属性合并为部分(保留顺序)。
局部的&
//还原任何必需的L-独占属性。
挑&
//还原任何必需的R属性。
挑
>
/**将属性从'R'合并到'L',就像spread操作符一样*/
型吊杆<
L扩展对象,
R扩展对象,
P/F(L&R)
>=P扩展了R的键
? (未定义的扩展R[P]?L[Extract]| R[P]:R[P])
:L[摘录]
/**始终定义的属性名称*/
类型RequiredProps={
[P in keyof T]-?:未定义扩展T[P]?从不:P
}[keyof T]
/**消除交叉点*/
类型Id={[P in keyof T]:T[P]}

我找到了一种语法来声明一个类型,该类型合并了任意两个对象的所有属性

type Merge<A, B> = { [K in keyof (A | B)]: K extends keyof B ? B[K] : A[K] };
type Merge={[K in keyof(A | B)]:K扩展keyof B?B[K]:A[K]};

这不太可能是真的。你可以从OP中尝试
merge({a:42},{b:foo,a:bar}).b
。在你的第一个例子中,合并的结果将是{a:1}。通过“显式”使用交叉点将其正确地键入为{a:number}您的意思是
a&B
?这就是你要找的吗?这看起来不错!在我的例子中,赋值仅用于对象文字,所以大多数边缘情况都可以!我确实对您对
OptionalPropertyNames
的修复有问题。
Spread
类型投诉,这是一个错误。我很想修好!Prettier删除了
-
,这是关键:)是否有一种将3+个对象合并为单个对象类型的解决方案?当然,您可以使用
Spread
或类似方法。我假设您想要像元组类型一样的东西
[a,B,C,D,E,F]
并自动将其转换为<
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
type Id<T> = { [K in keyof T]: T[K] }

type Foo = { a: string } & { b: number };
type IdFoo = Id<Foo>; // {a: string, b: number }
export type Spread<L extends object, R extends object> = Id<
  // Merge the properties of L and R into a partial (preserving order).
  Partial<{ [P in keyof (L & R)]: SpreadProp<L, R, P> }> &
    // Restore any required L-exclusive properties.
    Pick<L, Exclude<keyof L, keyof R>> &
    // Restore any required R properties.
    Pick<R, RequiredProps<R>>
>

/** Merge a property from `R` to `L` like the spread operator. */
type SpreadProp<
  L extends object,
  R extends object,
  P extends keyof (L & R)
> = P extends keyof R
  ? (undefined extends R[P] ? L[Extract<P, keyof L>] | R[P] : R[P])
  : L[Extract<P, keyof L>]

/** Property names that are always defined */
type RequiredProps<T extends object> = {
  [P in keyof T]-?: undefined extends T[P] ? never : P
}[keyof T]

/** Eliminate intersections */
type Id<T> = { [P in keyof T]: T[P] }
type Merge<A, B> = { [K in keyof (A | B)]: K extends keyof B ? B[K] : A[K] };