如何正确合并两种对象类型?

问题描述 投票:0回答:1

我正在开发用于合并对象的 TypeScript 泛型,但我遇到了测试用例

t11
失败的问题。

我想了解其背后的原因以及如何修改

MergeTypesWithPriority
以使所有测试用例通过。

MergeTypesWithPriority
旨在优先考虑 T 中的类型,并在类型为对象时具有递归逻辑。

type Expect<T extends true> = T
type Equal<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false

  type MergeTypesWithPriority<T, U> = {
    [K in keyof T | keyof U]: K extends keyof T
        ? K extends keyof U
            ? T[K] extends object
                ? U[K] extends object
                    ? T[K] extends any[]
                        ? T[K]
                        : U[K] extends any[]
                            ? U[K]
                            : MergeTypesWithPriority<T[K], U[K]>
                    : T[K]
                : T[K]
            : T[K]
        : K extends keyof U
            ? U[K] extends object
                ? T extends Record<K, any> 
                    ? U[K] extends any
                        ? T[K]
                        : U[K]
                    : U[K]
                : U[K]
            : never;
};


type t1 = Expect<Equal<MergeTypesWithPriority<{ a: string }, { a: number }>, { a: string }>>;
type t2 = Expect<Equal<MergeTypesWithPriority<{ }, { a: number }>, { a: number }>>;
type t3 = Expect<Equal<MergeTypesWithPriority<{ a: number | undefined }, { a: number }>, { a: number | undefined }>>;
type t4 = Expect<Equal<MergeTypesWithPriority<{ a: { b: number } }, { a: { b: string } }>, { a: { b: number } }>>;
type t5 = Expect<Equal<MergeTypesWithPriority<{ a: { c: number } }, { a: { b: string } }>, { a: { b: string; c: number } }>>;
type t6 = Expect<Equal<MergeTypesWithPriority<{ a: string }, { a: any }>, { a: string }>>;
type t7 = Expect<Equal<MergeTypesWithPriority<{ a?: number }, { a: any }>, { a: number | undefined }>>;

interface Items { foo: number; bar: { baz: number[] }}
type t8 = Expect<Equal<MergeTypesWithPriority<{ a: Items[] }, { a: any[] }>, { a: Items[] }>>;
type t9 = Expect<Equal<MergeTypesWithPriority<{ a: string }, { a: Items }>, { a: string }>>;
type t10 = Expect<Equal<MergeTypesWithPriority<{ a: any }, { a: Items }>, { a: any }>>;
type t11 = Expect<Equal<MergeTypesWithPriority<{ a: Items }, { a: any }>, { a: Items }>>;

游乐场

typescript
1个回答
0
投票

any
类型是故意不健全。它既充当顶部类型(如
unknown
),也充当(几乎)底部类型(如
never
类型
)。
any
可以分配给其他类型或从其他类型分配(除了
any
不能分配给
never
)。 这意味着如果要检查的类型是 any,代码中的许多
条件类型
的行为将与您的预期不同。 如果
U[K] extends object
U[K],您的
any
检查
 通过
U[K] extends any[]
检查也是如此。 并且
U[K] extends any
检查总是通过。

因此,当

U
{a: any}
时,您的示例代码将确定
a
属性是一个数组并返回它。哎呀。

如果您想以特殊方式处理

any
,您需要更改支票。

我知道如何执行此操作的唯一可靠方法是如 Typescript check for the 'any' typeDisallow call with any 中所述,它利用了不健全性。 TypeScript 会将几乎所有 unionintersection

any
转换为
any
。 像
1 extends (0 & T)
这样的东西似乎总是错误的,因为据说
X extends (Y & Z)
需要
X extends Y
Y extends Z
。 由于
1 extends 0
是假的,所以它也应该始终是假的。 但如果
T
any
,那么
0 & T
也是
any
(不健全、不安全,但有意),然后
1 extends any
true。 所以你可以用不健全来检测
any

我将重写你的代码的一些相关部分:

type MergeTypesWithPriority<T, U> = 1 extends (T & U & 0) ? T : { // <--
    [K in keyof T | keyof U]: K extends keyof T
        ? K extends keyof U
            ? T[K] extends object
                ? U[K] extends object
                    ? T[K] extends any[]
                        ? T[K]
                        : U[K] extends any[]
                            ? 1 extends (U[K] & 0) ? T[K] : U[K] // <--
                            : MergeTypesWithPriority<T[K], U[K]>
                    : T[K]
                : T[K]
            : T[K]
        : K extends keyof U
            ? U[K] extends object
                ? T extends Record<K, any> 
                    ? 1 extends (U[K] & 0) // <--
                        ? T[K]
                        : U[K]
                    : U[K]
                : U[K]
            : never;
};

如果

T
U
any
,则顶部的检查会提前退出,如果是,则返回
T
。中间的检查修复了将
any
错误分类为数组的问题,只需在继续之前返回
T[K]
(如果
U[K]
any
)即可。底部的检查会更改您的
U[K]
检查,因此它实际上会查找
any
(尽管我没有看到任何重要的示例)。

让我们测试您的代码:

type t11 = Expect<Equal<
  MergeTypesWithPriority<{ a: Items }, { a: any }>, 
  { a: Items }>
>; // okay

所有其他测试都通过了,现在这个也通过了。

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.