2

Story

I'm using konva library with Typescript which is HTML5 Canvas JavaScript framework.

Here is a simple tutorial code that I changed from javascript to 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);

But my Visual Studio Code shows error message like below.

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)

The class Shape is in Shape.d.ts and Rect is in Rect.d.ts. And I can see that Rect extended Shape! but VSC shows an error.

If I add the two property which is in Rect.d.ts to Shape class, the error is gone.

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
    ...
    ...

Question

I know that layer.add() function accept only Shape type. But as you know Typescript allows downcasting like below.

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

I cannot understand why layer.add() cannot accept Rect type even though it extended Shape type. What did I miss?

I need your advice. Thanks.

Community
  • 1
  • 1
Byeongin Yoon
  • 3,233
  • 6
  • 26
  • 44
  • Have you try layer.add( box) ? – CharybdeBE May 08 '19 at 09:01
  • @CharybdeBE error : `Conversion of type 'Rect' to type 'Shape' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.` – Byeongin Yoon May 08 '19 at 09:07

1 Answers1

2

The definitions for the library are apparently not tested with strictFunctionTypes unfortunately.

If this option is not activated, functions relate bivariantly, but under strictFunctionTypes functions relate contravariantly with regard to parameter types. This means:

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

The error you are getting is on hitFunc (and if that is fixed on sceneFunc) these are functions that take a second parameter of this, in effect Shape<ShapeConfig>. And this triggers the error when we try to assign the hitFunc in Rect that has takes a second parameter, also typed as this but in effect Rect. You can't assign the function with the derived parameter type to a function signature that would accept any base type argument.

There are multiple possible solution:

  1. Use a type assertion

    layer.add(box as unknown as Shape<ShapeConfig>);
    
  2. Disable strictFunctionTypes (the error goes away with strictFunctionTypes: false in tsconfig)

  3. Submit a pull request to definitely typed to make hitFunc and sceneFunc behave bivariantly event under strictFunctionTypes: true as described here. In this case this would work and preserve the functionality:

    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>;
    }
    
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357