简体   繁体   中英

Update dynamic key in class in Typescript

I have a class, for example:

class Team {
  pointsFor: number;
  pointsAgainst: number;
  
  constructor(){
    this.pointsFor = 0;
    this.pointsAgainst = 0;
  }
}

Now I would like to make a method for this class where I can update the property by key for example:

updateStats = (bool: boolean, property: string, increment: number) =>{
  const key: keyof this = bool ? `${property}For` : `${property}Against`
  this[key] += increment
}

However I get an error that string is not assignable to keyof this, and if I force the type like

const key: keyof this = bool ? `${property}For` as keyof this : `${property}Against` as keyof this

then I get an error: "Operator '+=' cannot be applied to types 'this[keyof this]' and 'number'."

Is there a way to accomplish what I want here where I call team.updateStats(true, 'points', 2) and update pointsFor ?

I really wouldn't recommend using string manipulation with TypeScript class properties the way you are trying to.

By using keyof this , you are essentially referring to all the keys of the class, like pointsFor: number , pointsAgainst: number , and updateStats: Function . As you can see, not all of them are numbers.

Another issue is that a string can be anything, so updateStats(true, 'blah blah', 3) could become valid if TypeScript didn't stop you.

It would be much better to constrain the specific properties you need to an object within the class with clearly defined properties.

For example:

class Team {
  teamProps: { [key: string]: { for: number, against: number }} = {
    points: { for: 0, against: 0 }
  }

  updateStats = (bool: boolean, property: keyof typeof this.teamProps, increment: number) =>{
    this.teamProps[property][bool ? 'for' : 'against'] += increment;
  }
}

You can access these properties with <Team>.teamProps.points.for instead of <Team>.pointsFor .

You can do nearly everything with TypeScripts typesystem:

TeamProp takes a union of keys ( keyof this in this case) and returns only strings, that match ${string}For or ${string}Against .

type TeamProp<K> = K extends `${infer P}For` ? P : K extends `${infer P}Against` ? P : never;

class Team {
    pointsFor: number;
    pointsAgainst: number;

    constructor() {
        this.pointsFor = 0;
        this.pointsAgainst = 0;
    }

    updateStats<P extends TeamProp<keyof this>>(bool: boolean, property: P, increment: number) {
        const key = bool ? `${property}For` : `${property}Against`
        this[key] += increment
    }
}

const t = new Team()

// Works
t.updateStats(true, 'points', 1);

// Argument of type '"foo"' is not assignable to parameter of type '"points"'.
t.updateStats(true, 'foo', 1);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM