简体   繁体   中英

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?

func3 expects Interface1 | Interface2 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

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);

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