Home > Software engineering >  Typescript best practice for "Types of parameters 'X' and 'X' are incompati
Typescript best practice for "Types of parameters 'X' and 'X' are incompati

Time:05-18

I have a question in typescript and want to know the best way in this situation

demo-link

// Params and Option are defined by third-party lib so it cannot be changed
interface Params {
  name: string;
  age: number;
}

interface Option {
  callback:  (params: Params | Params[]) => void
}

// Here is my code
const optionError:Option = {
  // in this case, the type of params must be Params
  callback:(params: Params) => { // Type '(params: Params) => void' is not assignable to type '(params: Params | Params[]) => void'.
    console.log(params.name)
    console.log(params.age)
  }
}

const optionOK:Option = {
  callback:(params) => {
    const p = params as Params; // Don't want to define a same parameter, what else can I do?
    console.log(p.name)
    console.log(p.age)
  }
}

I dont't want to define a same parameter, is there another way?

CodePudding user response:

I am not sure that's what you're looking for, but this should solve your issue:

interface Params {
  name: string;
  age: number;
}

interface Option {
  callback:  (params: Params | Params[]) => void
}

const optionError:Option = {
  callback:(params: Params | Params[]) => {
    if (!Array.isArray(params)) {
      console.log(params.name)
    }
  }
}

const optionOK:Option = {
  callback:(params: Params | Params[]) => {
    if (!Array.isArray(params)) {
      console.log(params.name)
    }
  }
}

We're typing the callback params as it should be, and then checking if the params is of type Params or Params[] by using Array.isArray(). And typescript figures out that if it's not an array, it is Params.

Also, avoid using params as Params as much as possible, you're outpassing typescript typings and that can lead to runtime errors if you're not entirely sure that the passed argument will be of type Params

CodePudding user response:

THis is because of contravariance.

There is an UNSAFE trick, which allows you to get rid of TypeScript errors. You just need to convert your callback property to a method (get rid of arrow function).

According to TS docs, TS methods are bivariant.


interface Params {
  name: string;
  age: number;
}

interface Option {
  callback(params: Params | Params[]): void // change is here
}

const optionError: Option = {
  callback: (params: Params) => {
    console.log(params.name)
  }
}

const optionOK: Option = {
  callback: (params: Params[]) => {
    params.map(e => e) // ok
  }
}

However this is SUPER UNSAFE. If you want it to make safe, use @jeremynac's answers

CodePudding user response:

Not sure if this is "best practice", but if your intention is to say that to comply with the Option type you need to implement the callback to accept Params or Params[] (or maybe both) you need to explicitly state that:

interface Params {
  name: string;
  age: number;
}

interface OptionWithParams {
  callback:  (params: Params) => void
}

interface OptionWithParamsArray {
  callback:  (params: Params[]) => void
}

type Option = OptionWithParamsArray|OptionWithParams;

Then all the following are valid:


const optionOk1:Option = {
  callback:(params: Params) => {
    console.log(params.name)
  }
}


const optionOk2:Option = {
  callback:(params: Params[]) => {
    console.log(params[0].name)
  }
}
const optionOk3:Option = {
  callback:(params: Params|Params[]) => {
    console.log('It\'s all good');
  }
}

The last case should work as well if you allow implict any but you shouldn't need that.

Playground link

  • Related