简体   繁体   中英

I want to define a type of callback function , one parameter is boolean , another parameter's type depends on this boolean type

I am writing a callback function type.

One parameter called A is boolean, another parameter called B depends on this boolean parameter:

  • if A is true, B will be an object

  • if A is false, B will be undefined

like this

login({callback : (success , res) => {
    if(success === true){
        console.log(res.a) //ok
    } else{
        console.log(res.a) // error: res is undefined
    }
}})

I wrote a type but it does not work

interface Options {
    callback?: <T extends boolean>(success: T ,res: T extends true? {a : string}:never) => void;
}

login({callback : (success , res) => {
    if(success === true){
        console.log(res.a) //ok
    } else{
        console.log(res.a) // also ok
    }
}})

What you want to do is not possible. When you type a function with generic types, you type the parameters and the return values. The generic types are resolved/inferred in the code where the function is being called and it verifies the consistency of the parameters and return value types.

Inside the implementation, the types do not resolve so they remain T and T extends true? {a: string }: never T extends true? {a: string }: never .

The best thing you could do is use type checks outside callback 's implementation, and use function overloading instead of conditional type, in order to make the second parameter optional:

function login({ callback }: { callback: Callback }) {
    const success = !Math.floor(Math.random() * 2);
    if (success) {
        callback(success, { a: 'test' }); // OK
        callback(success); // Fails
    }
    else {
        callback(success, { a: 'test' }); // Fails
        callback(success); // OK
    }
}

interface Callback {
    (success: true, res: { a: string }): void;
    (success: false): void;
}

TypeScript playground

From the point of view of the implementation of login() , it would be best for the callback property of the function parameter to accept arguments which are either of type [true, {a: string}] or of type [false, undefined?] . Traditionally you would write this as an overloaded function with multiple call signatures:

function login({ callback }: {
  callback: {
    (success: true, res: { a: string }): void;
    (success: false, res?: undefined): void;
  }
}) {
  callback(true, { a: "hey" });
  callback(false);
}

You will get a little more type safety if instead you represent the function as taking a rest parameter of a tuple type , or rather, a union of tuple types:

function login({ callback }: {
  callback: (...args:
    [success: true, res: { a: string }] |
    [success: false, res?: undefined]
  ) => void
}) {
  callback(true, { a: "hey" });
  callback(false);
}

When calling callback(true, {a: "hey"}) or callback(false) , either version work, but you can't really get type safety when implementing the callback itself with overloads. For the rest of this answer I will assume you're using the union-of-tuple-rest-parameter approach.


The union [success: true, res: {a: string}] | [success: false, res?: undefined] [success: true, res: {a: string}] | [success: false, res?: undefined] is a discriminated union , where the first element (with an index of 0 ) is the discriminant property. If you have a value args , you can check args[0] to determine whether or not the second element (with an index of 1 ) is a {a: string} or undefined .

So when calling login() , you can get type safety if you use args as a rest parameter and check its 0 and 1 indices:

login({
  callback: (...args) => {
    if (args[0]) {
      console.log(args[1].a) // okay
    } else {
      console.log(args[1].a) // error: undefined
    }
  }
})

But you can't get the same safety with an arrow function that takes two separate parameters instead of a rest parameter:

login({
  callback: (success, res?) => {
    if (success) {
      console.log(res.a) // error!
    } else {
      console.log(res.a) // error
    }
  }
})

The compiler does not track the correlation between success and res . This is a current limitation in TypeScript. It still doesn't track the correlation if you use destructuring assignment :

login({
  callback: (...[success, res]) => {
    if (success) {
      console.log(res.a) // error!
    } else {
      console.log(res.a) // error
    }
  }
})

Both of these run into the same problem where assigning the pieces of the union to success and res variables break the correlation. TypeScript 4.4 added a feature , as implemented in microsoft/TypeScript#44730 , which helps a little , as it lets you use success to narrow args , but it still doesn't let you use success to narrow res :

login({
  callback: (...args) => {
    const [success, res] = args;
    if (success) {
      console.log(res.a) // error
      console.log(args[1].a) //ok
    }
  }
})

In that pull request it says:

[T]he pattern of destructuring a discriminant property and a payload property into two local variables and expecting a coupling between the two is not supported as the control flow analyzer doesn't "see" the connection. ... We may be able to support that pattern later, but likely not in this PR.

So there's some hope that in a future version of TypeScript you can write (success, res?) =>... and things will magically work. For now, though, you have to refactor your code a bit.


And this, of course, leaves aside a more simple refactoring: just forget success entirely and use res instead:

function fixLogin(cb: (res?: { a: string } | undefined) => void) {
  login({ callback: (success, res?) => cb(res) });
}

fixLogin((res?) => {
  if (res) {
    console.log(res.a) // okay
  } 
})

After all, success doesn't give you any additional information over res . If res is present and defined, then success had to be true , and if res is undefined , then success had to be false . For all I know your code is a toy example and a similar refactoring isn't possible, but I would be remiss in not pointing this out, since it considerably simplifies both the call to and implementation of login() .

Playground link to code

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