juztcode
juztcode

Reputation: 1355

typescript - writing a function signature for multiple parameter types

I have the following:

const func1 = (state: Interface1){
//some code
}

const func2 = (state: Interface2){
//some other code
}

const func3: (state: Interface1|Interface2){
//some other code
}

but,

func3(func1)

gives me an error.

What I was trying to make was a function func3 that can take either func1 or func2 as a parameter. How do I do this in typescript?

Upvotes: 1

Views: 1494

Answers (2)

func3 expects Interface1 | Interface2 when you want to pass func1.

func1 has completely different type signature:

(state: Interface1) => void

If you want to pass a callback, you need to change func3 argument type:


type Interface1 = {
    tag: 'Interface1'
}

type Interface2 = {
    tag: 'Interface2'
}
const func1 = (state: Interface1) => {
    //some code
}

const func2 = (state: Interface2) => {
    //some other code
}

const func3 = (cb: typeof func1 | typeof func2) => {
    //some other code
}

func3(func1) // ok

If you want to compose functions this article might be interesting for you

UPDATE

Previous solutions works, but it is impossible to call callback. My bad, sorry for that.

In order to do that, you have to refactor your code a bit. I thought that simple union type should help, but we have a deal with functions.

It is not that easy.

Consider next example:


type Union = typeof func1 | typeof func2

const func3 = (cb: Union) => {
    cb({ tag: 'Interface2' }) // error
}

func3(func1) // ok

In above case cb is infered to (arg:never)=>any. Why ?

Please take a loot at @jcalz great answer regarding intersection.

The main point is - that types in contravariant positions get intersected.

And because you can't create Interface1 & Interface2 it resolved to never.

If you are wonder what is contravariance, variance etc ... please take a loot at my question.

So, TS does not know which argument is allowed and which is not.

As you see in the example, I have passed func1 as an argument but tried to call callback with Interface2 - it can throw an error in runtime.

You can handle it in this way:

type Interface1 = {
    tag: 'Interface1'
}

type Interface2 = {
    tag: 'Interface2'
}


const func1 = (state: Interface1) => {
    //some code
}

const func2 = (state: Interface2) => {
    //some other code
}

type Fn = (...args: any[]) => any


function func3<Cb extends Fn, Param extends Parameters<Fn>>(cb: Cb, ...args: Param) {
    cb(args)

}

const x = func3(func1, { tag: 'Interface1' }) // ok

Function arguments are contravariant to each other if you want to create union of functions that why it throws an error

UPDATE 3

Ok, ok, this may be not what you expect.

If you still want to compute func1 argument inside func3, you need to use typeguard


type Interface1 = {
    tag: 'Interface1'
}

type Interface2 = {
    tag: 'Interface2'
}
const func1 = (state: Interface1) => {
    //some code
}

const func2 = (state: Interface2) => {
    //some other code
}

type Fn = (a: any) => any

// typeguard
const isFunc = <R extends typeof func1 | typeof func2>(cb: Fn, cb2: R): cb is R => cb === cb2

const func3 = (cb: typeof func1 | typeof func2) => {
    if (isFunc(cb, func1)) {
        cb({ tag: 'Interface1' })
    } else {
        cb({ tag: 'Interface2' })
    }

}

func3(func1) // ok

Upvotes: 2

Majid M.
Majid M.

Reputation: 4974

As you know, typescript is a strict language. So you should specify the exact parameter in func3. You can do something like this:

type Interface1={
//some code
}
type Interface2={
//some code
}
type Interface3 = ((state:Interface1)=>void) | ((state:Interface2)=>void)

const func1 = (state: Interface1)=>{
//some code
}

const func2 = (state: Interface2)=>{
//some other code
}

const func3= (state: Interface3)=>{
//some other code
}
//both of following func3 call is correct
func3(func1);
func3(func2);

Upvotes: 1

Related Questions