typescript 如何测试两个类型是否完全相同

laik7k3q  于 2022-11-18  发布在  TypeScript
关注(0)|答案(7)|浏览(492)

bounty将在14小时后过期。回答此问题可获得+50声望奖励。brillout希望吸引更多人关注此问题。

下面是我的第一次尝试:(Playground链接)

/** Trigger a compiler error when a value is _not_ an exact type. */
declare const exactType: <T, U extends T>(
    draft?: U,
    expected?: T
) => T extends U ? T : 1 & 0

declare let a: any[]
declare let b: [number][]

// $ExpectError
exactType(a, b)

相关:https://github.com/gcanti/typelevel-ts/issues/39

xienkqul

xienkqul1#

啊,type-level equality operator. @MattMcCutchen提出了一个涉及泛型条件类型的解决方案,它在检测两个类型何时完全相等(而不仅仅是相互可赋值)方面做得很好。在一个完美的类型系统中,“相互可赋值”和“相等”可能是同一回事,但TypeScript并不完美。特别是,any类型既可赋值给任何其它类型,也可从任何其它类型赋值,这意味着string extends any ? true : falseany extends string ? true: false都求值为true,尽管stringany不是相同类型。
这里有一个IfEquals<T, U, Y, N>类型,如果TU相等,它的计算结果为Y,否则为N

type IfEquals<T, U, Y=unknown, N=never> =
  (<G>() => G extends T ? 1 : 2) extends
  (<G>() => G extends U ? 1 : 2) ? Y : N;

让我们来看看它的工作原理:

type EQ = IfEquals<any[], [number][], "same", "different">; // "different"

好的,这些被识别为不同的类型。可能还有一些其他的边缘情况,你认为相同的两个类型被视为不同的,反之亦然:

type EQ1 = IfEquals<
  { a: string } & { b: number },
  { a: string, b: number },
  "same", "different">; // "different"!

type EQ2 = IfEquals<
  { (): string, (x: string): number },
  { (x: string): number, (): string },
  "same", "different">; // "different", as expected, but:

type EQ3 = IfEquals<
  { (): string } & { (x: string): number },
  { (x: string): number } & { (): string },
  "same", "different">; // "same"!! but they are not the same, 
// intersections of functions are order-dependent

无论如何,给定这个类型,我们可以创建一个生成错误的函数,除非这两个类型以这种方式相等:

/** Trigger a compiler error when a value is _not_ an exact type. */
declare const exactType: <T, U>(
  draft: T & IfEquals<T, U>,
  expected: U & IfEquals<T, U>
) => IfEquals<T, U>

declare let a: any[]
declare let b: [number][]

// $ExpectError
exactType(a, b) // error

每个参数都有一个类型TU(用于泛型参数的类型推断)与IfEquals<T, U>相交,因此除非TU相等,否则将出现错误。我认为这给出了您想要的行为。
注意,这个函数的参数不是可选的,我不知道为什么你希望它们是可选的,但是(至少在--strictNullChecks打开的情况下)它削弱了这样做的检查:

declare let c: string | undefined
declare let d: string
exactType(c, d) // no error if optional parameters!

这取决于你
总之,希望能有所帮助,祝你好运!

mum43rcc

mum43rcc2#

edit:最完善的版本可以在here中找到

以下是我迄今为止找到的最强大的解决方案:

// prettier-ignore
type Exact<A, B> = (<T>() => T extends A ? 1 : 0) extends (<T>() => T extends B ? 1 : 0)
    ? (A extends B ? (B extends A ? unknown : never) : never)
    : never

/** Fails when `actual` and `expected` have different types. */
declare const exactType: <Actual, Expected>(
    actual: Actual & Exact<Actual, Expected>,
    expected: Expected & Exact<Actual, Expected>
) => Expected

感谢@jcalz为我们指明了正确的方向!

rt4zxlrg

rt4zxlrg3#

我写了一个库,tsafe,它可以让你这样做。

感谢@jcalz,您的回答帮助我们实现了这一目标!

k3fezbri

k3fezbri4#

到目前为止,我所见过的最健壮的Equals(尽管还不完美)是:

type Equals<A, B> = _HalfEquals<A, B> extends true ? _HalfEquals<B, A> : false;

type _HalfEquals<A, B> = (
    A extends unknown
        ? (
              B extends unknown
                  ? A extends B
                      ? B extends A
                          ? keyof A extends keyof B
                              ? keyof B extends keyof A
                                  ? A extends object
                                      ? _DeepHalfEquals<A, B, keyof A> extends true
                                          ? 1
                                          : never
                                      : 1
                                  : never
                              : never
                          : never
                      : never
                  : unknown
          ) extends never
            ? 0
            : never
        : unknown
) extends never
    ? true
    : false;

type _DeepHalfEquals<A, B extends A, K extends keyof A> = (
    K extends unknown ? (Equals<A[K], B[K]> extends true ? never : 0) : unknown
) extends never
    ? true
    : false;

例如,对于Equals<[any, number], [number, any]>,它会失败。
可在此处找到:https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-845655557

yshpjwxd

yshpjwxd5#

我有点恼火的是,其他命题暗示我只得到false,没有任何细节来理解它为什么失败。
这是我如何解决我的用例(它给出可读错误):

type X = { color: string };
type Y = { color: string };
type Z = { color: number };

const assert = <A, B extends A, C extends B>() => {}

/** Pass! */
assert<X, Y, X>(); 

/**
 * Fail nicely:
 * Type 'Z' does not satisfy the constraint 'X'.
 * Types of property 'color' are incompatible.
 * Type 'number' is not assignable to type 'string'.
 */
assert<X, Z, X>();
0qx6xfy6

0qx6xfy66#

如果您正在寻找一个没有任何第三方库依赖性的纯typescript解决方案,那么这个解决方案应该适合您

export function assert<T extends never>() {}
type TypeEqualityGuard<A,B> = Exclude<A,B> | Exclude<B,A>;

和用法如

assert<TypeEqualityGuard<{var1: string}, {var1:number}>>(); // returns an error
assert<TypeEqualityGuard<{var1: string}, {var1:string}>>(); // no error
eoxn13cs

eoxn13cs7#

我们应该根据不同的问题采取不同的方法,例如,如果我们知道我们要与任何一个数字进行比较,我们可以使用typeof()
例如,如果我们要比较接口,我们可以使用以下方法:

function instanceOfA(object: any): object is A {
    return 'member' in object;
}

相关问题