Search code examples
typescriptdeclarationmemberunion-types

how to set typescript member variables that are unknown in constructor


I am learning Typescript and want to know how to define class member variables properly.

At the moment, I am declaring them in the class, above the constructor, but in order to make sure that they are instantiated in the constructor (or I get a warning), I declare them as whatever type | null. This means that if I can then instantiate the member variable in the constructor by setting equal to null, and then setting to its proper value, when I am able.

I don't know that this is a correct method to declare member variables. As an example :

export default class FlowerBed {
  canv:HTMLCanvasElement | null;
  ctx:CanvasRenderingContext2D | null;
  p2d:Path2D;
  t:number;
  currentPoint:Array<number>;
  to:number = 0;
  image:HTMLImageElement | null;
  imagesrc:CanvasImageSource | null;
  pattern:CanvasPattern | null;

  constructor (garden:HTMLCanvasElement) {
    this.image = null;
    this.imagesrc = null;
    this.canv = null;
    this.ctx = null;
    this.pattern = null;
    this.to = 0;
    this.p2d = new Path2D();
    this.t = 0;
    this.currentPoint = [160,350];  
  }

I don't have the value of canvas,ctx, pattern etc at the time of construction.

Is there a more correct way of proceeding?

Many thanks...


Solution

  • So if you don't need to use null with special meaning you can simplify your class

    export default class FlowerBed {
      canv?: HTMLCanvasElement;
      ctx?: CanvasRenderingContext2D;
      p2d: Path2D = new Path2D();
      t: number = 0;
      currentPoint: Array<number> = [160, 350];
      to: number = 0;
      image?: HTMLImageElement;
      imagesrc?: CanvasImageSource;
      pattern?: CanvasPattern;
    
      constructor(garden: HTMLCanvasElement) {
        // ...
      }
    
      someMethod() {
        // Note the exclamation mark – it tells ts you are sure the property has a value
        return this.canv!.getBoundingClientRect();
      }
    }
    
    

    ? after field name means that the field can be missing what is almost the same as having it undefined. Depending on your experience you may prefer to put value assignments to the constructor, but you still can avoid null assignments here.

    It's worth noting that uninitialized properties do not present in the object, so runtime checks such as hasOwnProperty return false.

    const fb = new FlowerBed(canvasEl);
    fb.hasOwnProperty('ctx'); // false
    

    So if it is important you may initialize them but not necesseraly to do in in constructor.

    export default class FlowerBed {
      canv?: HTMLCanvasElement;
      ctx?: CanvasRenderingContext2D = undefined;
      // ...
    }
    

    It may be useful when there is a case when the property may get some nullish value in runtime and that is one of the reasons some developers distinguish undefined and null values, using undefined as "not yet initialized" and null as "empty or non-existent value"