I'm trying to write a function such that the first parameter is boolean
, and depending on whether this argument is true
or false
, the second argument is a function that accepts either a string
or string[]
.
Here is my attempt:
type P<B extends boolean> = B extends true ? string[] : string
function callback<B extends boolean>(b: B, t: (f: P<B>) => void) {
const file = 'file'
if (b) {
t([file]) // <-- Error: Argument of type 'string' is not assignable to parameter of type 'P<B>'.
} else {
t(file) // <-- Error: Argument of type 'string[]' is not assignable to parameter of type 'P<B>'.
}
}
callback(false, (f: string) => {}) // <-- No problem, resolves the correct argument type
callback(true, (f: string[]) => {}) // <-- No problem, resolves the correct argument type
This works for resolving the correct argument types when the function is called. However, inside the function, the TS compiler is giving me an error that it cannot resolve the conditional type to either string
or string[]
. What is the correct way to do this?
Playground link.
Javascript. type Conditional<G> = G extends { typeof : number|string|Boolean} ? Example 3: In this example first, we will create the conditional types for the number and string in one conditional type. After that, we use the same conditional types for both string and number.
Conditional types help describe the relation between the types of inputs and outputs. When the type on the left of the extends is assignable to the one on the right, then you'll get the type in the first branch (the “true” branch); otherwise you'll get the type in the latter branch (the “false” branch).
Using ?: with undefined as type definition While there are no errors with this interface definition, it is inferred the property value could undefined without explicitly defining the property type as undefined . In case the middleName property doesn't get a value, by default, its value will be undefined .
Use the typeof operator to check the type of a variable in TypeScript, e.g. if (typeof myVar === 'string') {} . The typeof operator returns a string that indicates the type of the value and can be used as a type guard in TypeScript.
Here is one way to do it by passing an object with both parameters. By using a type that has true
and the one signature or false
and the other, the compiler can differentiate the object by testing the b
property.
type f = { b: false, t: (f: string) => void };
type t = { b: true, t: (f: string[]) => void };
type fOrT = f | t;
function callback(x: fOrT) {
const file = 'file'
if (x.b) {
x.t([file])
} else {
x.t(file)
}
}
callback({ b: false, t: (f: string) => {} })
callback({ b: true, t: (f: string[]) => {} })
TypeScript Playground
You can narrow a type down based on another variable by writing custom typings, for instance
function instanceOfMyTYpe(textArray: string[] | string, flag: boolean): textArray is string {
return flag;
}
let file = "test" as string | string[];
file.toUpperCase(); // doesn't work. Property 'toUpperCase' does not exist on type 'string | string[]'.
if (instanceOfMyTYpe(file, true)) file.toUpperCase(); // but this works
else file.forEach((text)=>text.toUpperCase()); // and so does this
It will not link one variable with another, so invalid function calls can stil happen. For instance calling the function with an array and true flag, will make your program think that the array is actually a string, even though it isn't so be careful.
Using generic type arguments like this is a real pain in the neck, I'm not sure how this works exactly, but you can almost never make typescript narrow down the types. If you hover over t
inside the if
construct, you will see, that it has type (f: P<B>) => void
, not (f: string) => void
or (f: string[]) => void
: it wasn't able to narrow down the type of one variable, depending on another. I think this is a limitation and I can't think of any way to fix this for now. I may be wrong, but I've encountered this situation before in a bit more complex context and had to make changes to the design of the function to make it work.
I think in this case you can just do t([file] as any)
and t(file as any)
, finally, the point of these types is to force calling function in the correct way. If it's called correctly, then inside it knows what to do, I think it's worth adding a couple of any
s here.
Also you could use overloads to get rid of generics, but it doesn't solve the issue:
function callback(b: true, t: (f: string[]) => void): void
function callback(b: false, t: (f: string) => void): void
function callback(b: boolean, t: ((f: string) => void)) | ((f: string[]) => void)) {
//
}
One solution that would actually not need any
s is to use objects, but this is not ideal, because you may need to change other logic in your application:
type Options = {
b: true
t: (f: string[]) => void
} | {
b: false
t: (f: string) => void
}
function callback(options: Options) {
const file = 'file'
if(options.b) options.t([file])
else options.t(file)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With