skip to Main Content

I have class with constructor that can accept object of two types, and then based on what type of object it got, perform different actions.

I’ve seen that it’s possible to use as keyword for that.
But it feels wrong to me.

export type Angle = {
    cos: number; 
    sin: number
}
export type VectorAngleInit = {
    angle: Angle,
    magnitude: number
}
export type VectorDInit = {
    dx: number,
    dy: number
}
export class Vector {
    #dx: number;
    #dy: number;
    constructor(init: VectorAngleInit | VectorDInit) {
        if ((init as VectorAngleInit)?.angle) {
            const angleInit = init as VectorAngleInit
            this.#dx = angleInit.magnitude * angleInit.angle.cos;
            this.#dy = angleInit.magnitude * angleInit.angle.sin;
        } else {
            const dInit = init as VectorDInit
            this.#dx = dInit.dx
            this.#dy = dInit.dy
        }
    }
}

Should i do that another way? If so, which way is better you think?

2

Answers


  1. You can use in operator narrowing to check for the presence of "angle" in init and narrow init to the desired union member without a need for type assertions (what you’re calling "casting"):

    export class Vector {
      #dx: number;
      #dy: number;
      constructor(init: VectorAngleInit | VectorDInit) {
        if ("angle" in init) {
          this.#dx = init.magnitude * init.angle.cos;
          this.#dy = init.magnitude * init.angle.sin;
        } else {
          this.#dx = init.dx
          this.#dy = init.dy
        }
      }
    }
    

    Playground link to code

    Login or Signup to reply.
  2. The thing you need is called Type Predicate
    which is an official way to narrow down your union type. It is a function with a special return type x is SomeType which is actually a boolean but informs typescript compiler that what you actually needs to do is to predicate the type rather than just return a boolean.

    A sample related to your problem could be this:

    First define your type predicate function

    export type VectorAngleInit = {
      angle: Angle
      magnitude: number
    }
    
    // type predicate predicating whether u is of type VectorAngleInit
    export function isVectorAngleInit(u: unknown): u is VectorAngleInit {
      return !!u && typeof u === 'object' && (u as VectorAngleInit).angle instanceof Angle
    }
    
    export type VectorDInit = {
      dx: number
      dy: number
    }
    
    // type predicate predicating whether u is of type VectorDInit
    export function isVectorDInit(u: unknown): u is VectorDInit {
      return !!u && typeof u === 'object' && typeof (u as VectorDInit).dx === 'number'
    }
    

    And then use it in your constructor:

    export class Vector {
      #dx: number
      #dy: number
      constructor(init: VectorAngleInit | VectorDInit) {
        if (isVectorAngleInit(init)) {
          // notice that type of `init` is auto inferred within if block
          this.#dx = init.magnitude * init.angle.cos
          this.#dy = init.magnitude * init.angle.sin  
        }
        if(isVectorDInit(init)){
          this.#dx = init.dx
          this.#dy = init.dy  
        }
      }
    }
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search