网站搜索

konvajs - TypeScript 类型 a 不能分配给类型 b


故事

我正在使用带有 TypeScript 的 konva 库,这是一个 HTML5 Canvas JavaScript 框架。

这是一个简单的教程代码,我把它从JavaScript改成了TypeScript。

import Konva from "konva";

const stage = new Konva.Stage({
  container: 'container',
  width: window.innerWidth,
  height: window.innerHeight
});

const layer = new Konva.Layer();
stage.add(layer);

const box = new Konva.Rect({
  x: 50,
  y: 50,
  width: 100,
  height: 50,
  fill: '#00D2FF',
  stroke: 'black',
  strockWidth: 4,
  draggable: true
});

layer.add(box);

但是我的 Visual Studio Code 显示如下错误信息。

Argument of type 'Rect' is not assignable to parameter of type 'Group | Shape<ShapeConfig>'.
  Type 'Rect' is not assignable to type 'Shape<ShapeConfig>'.
    Types of property 'hitFunc' are incompatible.
      Type 'GetSet<(ctx: Context, shape: Rect) => void, Rect>' is not assignable to type 'GetSet<(ctx: Context, shape: Shape<ShapeConfig>) => void, Shape<ShapeConfig>>'.
        Type '(ctx: Context, shape: Rect) => void' is not assignable to type '(ctx: Context, shape: Shape<ShapeConfig>) => void'.
          Types of parameters 'shape' and 'shape' are incompatible.
            Type 'Shape<ShapeConfig>' is missing the following properties from type 'Rect': _sceneFunc, cornerRadiusts(2345)

类 Shape 在 Shape.d.ts 中,Rect 在 Rect.d.ts 中。我可以看到 Rect 继承了 Shape!但是 VSC 显示了一个错误。

如果我将 Rect.d.ts 中的两个属性添加到 Shape 类中,错误就消失了。

export declare class Shape<Config extends ShapeConfig = ShapeConfig> extends Node<Config> {
    _sceneFunc(context: any): void; // this was in Rect class
    cornerRadius: GetSet<number, this>; // this was in Rect class
    ...
    ...

问题

我知道 layer.add() 函数只接受 Shape 类型。但如你所知,Typescript 允许像下面这样进行向下转换。

class Base {
  baseProperty: string = "aaaa";
}
class A extends Base {
  aProperty: number = 1;
}
var a: A = new A();
function test(arg: Base) {
  console.log(arg.baseProperty);
}
test(a); // => No Error

我无法理解为什么 layer.add() 无法接受 Rect 类型,即使它继承了 Shape 类型。我错过了什么吗?

我需要你的建议。谢谢。

答案#1

遗憾的是,该库的定义显然没有使用 strictFunctionTypes 进行测试。

如果未激活此选项,函数是双向相关的,但在 strictFunctionTypes 下,函数在参数类型方面是逆相关的。这意味着:

class Animal { private x:undefined }
class Dog extends Animal { private d: undefined }

type EventHandler<E extends Animal> = (event: E) => void

let o: EventHandler<Animal> = (o: Dog) => { } // fails under strictFunctionTyes

你遇到的错误发生在 hitFunc 上(如果修复了它,错误会出现在 sceneFunc 上)。这些函数的第二个参数是 this,实际上是 Shape<ShapeConfig>。当我们尝试在 Rect 中分配 hitFunc 时,会触发这个错误,因为它的第二个参数同样被类型定义为 this,但实际上是 Rect。你不能将带有派生参数类型的函数分配给可以接受任何基类型参数的函数签名。

有多种可能的解决方案:

  1. 使用类型断言

    layer.add(box as unknown as Shape<ShapeConfig>);
    
  2. 禁用 strictFunctionTypes(在 tsconfig 中将 strictFunctionTypes: false,错误就会消失)

  3. 向 DefinitelyTyped 提交一个拉取请求,使 hitFunc 和 sceneFunc 即使在 strictFunctionTypes: true 下也能表现为双变量,如此处所述。在这种情况下,这将可行并保持功能性:

    export type ShapeConfigHanlder<TTarget> = {
        bivarianceHack(ctx: Context, shape: TTarget): void
    }['bivarianceHack']
    export interface Shape extends NodeConfig {
        ...
        hitFunc: GetSet<ShapeConfigHanlder<this>, this>;
        sceneFunc: GetSet<ShapeConfigHanlder<this>, this>;
    }