代码之家  ›  专栏  ›  技术社区  ›  aleclarson

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

  •  1
  • aleclarson  · 技术社区  · 6 年前

    这是我的第一次尝试:( 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

    3 回复  |  直到 6 年前
        1
  •  5
  •   jcalz    6 年前

    啊, type-level equality operator .@MattmcCutchen想出了一个 solution 涉及通用条件类型,当两个类型完全相等时,它可以很好地检测,而不是互相分配。在一个完美的声音类型系统中,“可相互分配”和“相等”可能是相同的事情,但打字稿不是完美的声音。尤其是, any 类型既可分配给任何其他类型,也可从任何其他类型分配,这意味着 string extends any ? true : false any extends string ? true: false 两者的值都为 true 尽管事实上 string 任何 不是同一类型。

    这里有一个 IfEquals<T, U, Y, N> 计算结果为的类型 Y 如果 T U 平等,并且 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
    

    每个参数都有一个类型 T U (用于一般参数的类型推断)与 IfEquals<T, U> 所以会有一个错误,除非 T U 一律平等。我想这就是你想要的行为。

    请注意,此函数的参数不是可选的。我真的不知道你为什么想要它们是可选的,但是 --strictNullChecks 打开)这样做会削弱检查:

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

    这件事由你决定。

    不管怎样,希望能有所帮助。祝你好运!

        2
  •  1
  •   aleclarson    6 年前

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

    // 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为我们指明了正确的方向!

        3
  •  -1
  •   Luis H. Moreno    6 年前

    我们应该针对这个问题采取不同的方法。例如,如果我们知道我们正在与任何一个比较数字,我们可以使用typeof()。

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

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