【问题标题】:How to test if two types are exactly the same如何测试两种类型是否完全相同
【发布时间】:2019-05-17 08:45:45
【问题描述】:

这是我的第一次尝试:(playground link)

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

【问题讨论】:

标签: typescript


【解决方案1】:

我们应该根据问题采取不同的方法。例如,如果我们知道我们正在将数字与任何数字进行比较,我们可以使用typeof()

如果我们比较接口,例如,我们可以使用这种方法:

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

【讨论】:

  • 抱歉,这些类型适用于将要执行的代码。它只是用于测试环境类型定义。至少我的用例是。
【解决方案2】:

啊,type-level equality operator。 @MattMcCutchen 提出了一个涉及通用条件类型的solution,它可以很好地检测两种类型何时完全相等,而不仅仅是相互分配。在一个完美的类型系统中,“mutually assignable”和“equal”可能是同一回事,但 TypeScript 并不完美。特别是,any 类型既可分配给任何其他类型,也可从任何其他类型分配,这意味着 string extends any ? true : falseany extends string ? true: false 都计算为 true,尽管事实上 stringany 并不相同类型。

这是一个 IfEquals&lt;T, U, Y, N&gt; 类型,如果 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

每个参数都有一个与IfEquals&lt;T, U&gt; 相交的类型TU(用于泛型参数的类型推断),因此除非TU 相等,否则将出现错误。我认为这会给出你想要的行为。

请注意,此函数的参数不是可选的。我真的不知道您为什么希望它们是可选的,但是(至少在打开 --strictNullChecks 的情况下)它会削弱这样做的检查:

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

这取决于你。

无论如何希望这会有所帮助。祝你好运!

【讨论】:

  • 这些知识将帮助数百万人。谢谢@jcalz!
  • 为什么需要expected: U &amp; IfEquals&lt;T, U&gt;?我认为draft: T &amp; IfEquals&lt;T, U&gt; 捕获了所有错误。
  • 我认为如果你只用U 替换U &amp; IfEquals&lt;T, U&gt;,那么exactType() 将接受TneverU 的参数,我假设你想要那个被排除在外。
  • any[]ReadonlyArray&lt;any&gt; 进行比较时,您的解决方案失败(请参阅此处:bit.ly/2TNxPop)。我已经用一个解决这个问题的解决方案更新了上面的要点。
  • 有人能解释一下为什么我们使用额外的 () => G extends T 助手吗?就是不明白
【解决方案3】:

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 指出正确的方向!

【讨论】:

  • 精确失败
【解决方案4】:

迄今为止我见过的最强大的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&lt;[any, number], [number, any]&gt; 失败。

在这里找到: https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-845655557

【讨论】:

    【解决方案5】:

    tsafe 让你这样做。

    感谢@jcalz,您的回答让这一切成为可能!

    【讨论】:

      【解决方案6】:

      我有点恼火,因为其他命题暗示我只得到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>(); 
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-10-19
        • 1970-01-01
        • 2022-06-16
        • 1970-01-01
        • 2016-02-26
        • 1970-01-01
        • 2022-07-06
        相关资源
        最近更新 更多