【问题标题】:contravariance issue in typescript interfaces?打字稿接口中的逆变问题?
【发布时间】:2022-11-23 02:38:02
【问题描述】:

如果我滥用该术语,请先行道歉,但我能够在打字稿中实现一个我认为类型不安全的接口,例如:

interface Point {
  x: number;
  y: number;
  dist (other: Point): number
}

GridPoint implements Point {
  constructor (public x: number, public x: number) {}
  dist (other: Point) { /* ... math here ... */ } //
}

NamedPoint implements Point {
  // this class has an extra `name: string` property...
  constructor (
    public x: number,
    public x: number,
    public name: string
  ) {}

  dist (other: NamedPoint) {
    // other is a NamedPoint so other.name is okay, but this
    // is not true of any Point, so how can NamedPoint be said
    // to implement point?
    if (other.name.startsWith()) { /* ... */ }
  }
}

// this will throw if `a` is a NamedPoint and `b` is a GridPoint
function getDist (a: Point, b: point) {
  console.log(`distance is: ${a.dist(b)}`)
}
// but tsc won't complain here:
getDist(new NamedPoint(1, 2, 'foo'), new GridPoint(9, 8));

link to full example on playground

同样,我确定我在“逆变”方面的措辞是错误的,但我认为 NamedPoint implements Point 会被编译器禁止。我以为我可以通过在 tsconfig 中打开 strictFunctionTypes 来获得它,但这显然不适用于这种情况。

是我对类型的理解不正确,还是这里的打字稿有误?如果是后者,我能做点什么吗?

【问题讨论】:

    标签: typescript inheritance types interface contravariance


    【解决方案1】:

    您对类型的理解是正确的,但是 TypeScript 在涉及到方法参数。它将方法的参数类型视为 bivariant。如您所见,这是不安全的。但 TypeScript 这样做很方便,否则某些内置的 JavaScript 类层次结构将不再创建类型层次结构。它还允许人们treat types like Array<T> as covariant in T...方便,但不安全。

    这种双变性过去适用于所有函数类型,但启用 --strictFunctionTypes compiler option 将这种不合理性限制在方法类型上。因此,您的解决方法是将 dist 从方法重新定义为函数类型的属性:

    interface Point<T> {
        x: T;
        y: T;
        dist: (other: Point<T>) => T // change
    }
    

    没有什么能阻止你实施函数类型的属性作为方法,因此您的 GridCoord 类仍然可以正确编译。但是现在 NamedPoint 给了你预期的错误:

    class NamedPoint implements Point<number> {
    
    // ✂ snip ✂ //
    
        dist(other: NamedPoint): number { // error!
      //~~~~ <-- Type '(other: NamedPoint) => number' is not 
      //         assignable to type '(other: Point<number>) => number'.
            if (other.name.startsWith(this.name)) return 0;
            else return Math.sqrt(
                (this.x - other.x) ** 2 +
                (this.y - other.y) ** 2
            );
        }
    
    }
    

    Playground link to code

    【讨论】:

      猜你喜欢
      • 2019-07-16
      • 1970-01-01
      • 2017-09-02
      • 1970-01-01
      • 2018-02-27
      • 2016-11-09
      • 2019-02-01
      • 1970-01-01
      相关资源
      最近更新 更多