Home > Mobile >  How can I make an undefined parameter not required?
How can I make an undefined parameter not required?

Time:06-01

Playground

I'm trying to make undefined values be optional, while non-optional values are optional. My perfect example would be:

type TypeObj = {
  a: undefined;
  b: number;
  c: string;
}

call("a"); call("b", 1); call("c", "3"); // should work
call("b", "a"); call("c", 3) // shouldn't work
call("b"); call("c") // also shouldn't work

but my current approach (with the second parameter being optional) allows calling b and c without the second parameter, which I do not want.

function call<K extends keyof TypeObj = keyof TypeObj>(key: K, param?: TypeObj[K]) {

}

call("a"); call("b", 1); call("c", "3"); // works (good)
call("b", "a"); call("c", 3) // doesn't work (good)
call("b"); call("c") // works (don't want that)

CodePudding user response:

This sort of thing essentially requires call() to have multiple call signatures. Traditionally this would need you to make call() an overloaded function, but you can also do it by having the function accept a tuple-typed rest parameter.

Here's one approach:

type OptionalIfUndefined<T> =
  undefined extends T ? [param?: T] : [param: T];

function call<K extends keyof TypeObj>(
  key: K, ...[param]: OptionalIfUndefined<TypeObj[K]>
) {
  const _param = param as TypeObj[K]; // <-- might need this
}

The OptionalIfUndefined<T> type is a conditional type that checks if undefined is assignable to T. If so, it evaluates to a single-element tuple with an optional element of type T; otherwise, it evaluates to a single-element tuple with a required element of type T.

Then call() is given a rest parameter of type OptionalIfUndefined<TypeObj[K]>. We use destructuring assignment so that param is a variable name holding the single element, since that's all we really want (the actual array holding it isn't useful to us).

In your example you didn't do anything inside the implementation of call(), but if you were expecting param to be known as type TypeObj[K] you'll be disappointed. A generic conditional type is essentially opaque to the compiler, so it doesn't know that typeof param will be anything narrower than TypeObj[K] | undefined. You'll need to assert it like const _param = param as TypeObj[K] and then use _param instead of param (or swap the names if you want) if you need such functionality.

Well, let's make sure it works:

call("a") // okay
call("a", undefined) // okay
call("b", 1); // okay
call("b") // error
call("c", "a"); // okay
call("c") // error

Looks good!

Playground link to code

  • Related