0
votes

I am trying to defined a basic interface with an arrow function as properties. However, it gives me a TS error.

interface InterfaceTest {
  input?: (test: number | string) => number | undefined;
}

const myVariable: InterfaceTest = {
  input: (test: number) => {
    return test;
  },
};

const myVariable2: InterfaceTest = {
  input: (test: string) => {
    return parseInt(test, 2);
  },
};

TS2322: Type '(test: number) => undefined' is not assignable to type '(test: string | number) => number | undefined'.    Types of parameters 'test' and 'test' are incompatible. Type 'string | number' is not assignable to type 'number'.  Type 'string' is not assignable to type 'number'.

In fact, I do not understand the error because I would like to have a string or a number as parameter to my function and it tells me that I can not only number. How could I do to have string or number as input of my arrow function ?

In addition, when I do that the error disappear

interface InterfaceTest {
  input?(test: number | string): number | undefined;
}

const myVariable: InterfaceTest = {
  input: (test: number) => {
    return test;
  },
};

const myVariable2: InterfaceTest = {
  input: (test: string) => {
    return parseInt(test, 2);
  },
};

Why ?

1
Your interface specifies number | string, but then myVariable just specifies number. These two should match.Evert
@Evert, so it is not possible to do what I am doing ? Check the update because it does not throw error when it is not arrow function into the Interface.PierBJX

1 Answers

2
votes

Simply put.. if the interface says that you can pass a string OR a number, then when you implement that interface.. that function also needs to be able to receive a string or a number.

If the implementation of your interface reduces the type from string | number to just string or just number, it breaks the contract.

so it is not possible to do what I am doing

It's a bit hard to say, because your example is a very artificial one and probably not representative of what you're actually trying to do.

The idea of using an interface, is that something else can receive an instance of that interface and rest assured that they can your function with either a string or a number. Clearly this is not true for your case, because myVariable only takes a number, and myVariable2 only takes a string.

It's possible to express the interface this way:

interface InterfaceTest {
   input?: (test: number) => number | undefined | (test: string) => number | undefined;
}

This interface states that if input exists, it must have an implementation of either of these functions, but you're going to run into trouble using this too, because now there's no easy to to figure out if the implementation of your interface is the number or string kind.

Which might mean a type guard or type assertion.

But back to the original point, I think the example is a bit nonsensical. What is the goal of the interface? Why is it an interface at all?

Under what circumstances do you need to be able to substitute myVariable with myVariable2.

Stripping the interface obviously works, so there must be a reason you are using the interface:

const myVariable = {
  input: (test: number) => {
    return test;
  },
};

const myVariable2 = {
  input: (test: string) => {
    return parseInt(test, 2);
  },
};

There is one way to solve this trough generics, but I hesitate to suggest this as I worry that perhaps you're not using the interface for the right reason.

But here it is:

Say your InterfaceTest has many functions, and might have an input function. But there's basically 2 variants of InterfaceTest, there's one variant that only works with numbers, and there's one that only works with strings.

The generic version of your example would look like this:

interface InterfaceTest<T> {
  input?(test: T): number | undefined;
}

const myVariable: InterfaceTest<number> = {
  input: (test: number) => {
    return test;
  },
};

const myVariable2: InterfaceTest<string> = {
  input: (test: string) => {
    return parseInt(test, 2);
  },
};