Home > Enterprise >  How do I refactor (fix) this method?
How do I refactor (fix) this method?

Time:01-02

I'm new with typescript and I kept getting the same "not assignable to parameter of type error" error, and, after some research, I solve it but I'm not proud of the solution.

I'm working with a simple class and to simplify I let only the attribute I'm working with.

interface Proficiencies {
  armor: string[];
  weapons: string[];
  tools: string[];
  savingThrows: number[];
  skills: boolean[];
}
class Character {
  public readonly proficiencies: Proficiencies = {
      armor: [],
      weapons: [],
      tools: [],
      savingThrows: [],
      skills: []
    }

// This is the code that have the error
public addProficiency<T extends keyof Proficiencies> (proficiencyType: T, proficiency: Proficiencies[T]) {
    this.proficiencies[proficiencyType].push(...proficiency);
  }
}

If I add 'as never[]' it works, but I wonder if there's any other way to make it transpile.

this.proficiencies[proficiencyType].push(...proficiency as never[]);

CodePudding user response:

First, The bool type is not a valid type in TypeScript. You should use the boolean type instead.

  interface Proficiencies {
  armor: string[];
  weapons: string[];
  tools: string[];
  savingThrows: number[];
  skills: boolean[]; <----
}

Second, the proficiency parameter type is not being inferred correctly by TypeScript, causing the error you're seeing. Please try like this:

 public addProficiency<T extends keyof Proficiencies, U extends Proficiencies[T]>(
      proficiencyType: T,
      proficiency: U
    ) {
      this.proficiencies[proficiencyType].push(...proficiency);
    }

CodePudding user response:

Your mistake is here:

    public readonly proficiencies: Proficiencies = {
          armor: [],
          weapons: [],
          tools: [],
          savingThrows: [],
          skills: []
        }

Without defining the array type, it by default will be never. So when you tried to add a ...proficiency to it, it was a type mismatch, and so it threw the error you saw.

Furthermore, you define proficiencies as an object while you try to call the push method which is available on arrays!

Your code :

    interface Proficiencies {
      armor: string[];
      weapons: string[];
      tools: string[];
      savingThrows: number[];
      skills: boolean[];
    }
    
    class Character implements Proficiencies {
      armor: string[] = [];
      weapons: string[] = [];
      tools: string[] = [];
      savingThrows: number[] = [];
      skills: boolean[] = [];
      
      public readonly proficiencies: Proficiencies = {
        armor: this.armor,
        weapons: this.weapons,
        tools: this.tools,
        savingThrows: this.savingThrows,
        skills: this.skills,
      };
      
      public addProficiency<T extends keyof Proficiencies>(
        proficiencyType: T,
        proficiency: Proficiencies[T]
      ) {
        return [this.proficiencies, ...proficiency];
      }
    }
 

CodePudding user response:

The fundamental issue is that you haven't made it "explicit enough" that every property of Proficiencies is an array of some type, where that type varies with the key. In addProficiency, this.proficiencies[proficiencyType] has type Proficiencies[T]. When Typescript looks for a push method on this object, it needs to simplify it to something it can do method lookup on. Right now, it can't think of anything better to do except union all the types of the fields of Proficiencies and say it has a string[] | number[] | boolean[]. Then Typescript knows this object must have a push method, but it isn't sure if that push takes ...items: string[] or number[] or boolean[], so plays it safe and says it takes ...items: never[] (which means it isn't useful).

You need to give Typescript a way to say that Proficiencies[T] is always Something<T>[]. Then, without needing to simplify, Something<T>, Typescript can say the push method takes ...items: Something<T>[]. Then, since this is now depending on T, it is possible to call push with a parameter of the right type.

You can do this by adding a helper type to map between keys and the element types of the arrays.

interface ProficiencyTypes {
    armor: string
    weapons: string
    tools: string
    savingThrows: number
    skills: boolean
}
type MapArray<T> = {
    [Key in keyof T]: T[Key][]
}
type Proficiencies = MapArray<ProficiencyTypes> // teach typescript "every key of Proficiencies is a key of ProficiencyTypes but with [] added to its type"
class Character {
  public readonly proficiencies: Proficiencies = {
    armor: [],
    weapons: [],
    tools: [],
    savingThrows: [],
    skills: []
  }
  public addProficiency<T extends keyof Proficiencies>(proficiencyType: T, proficiency: Proficiencies[T]) {
    this.proficiencies[proficiencyType].push(...proficiency);
  }
}

(Note that now you can't add non-array properties to Proficiencies. That is exactly the freedom that Typescript wants/needs you to give up in order to ensure a function like addProficiency makes sense.)

  • Related