Traditionally (pre TS 2.8 or TS 3.0) you could only use overloads to achieve this, but now I'd be inclined to use a tuple-typed rest parameter; specifically a union of such tuple types:
function useNavigation(...args:
[type: "horizontal" | "vertical", columns?: number] |
[type: "grid", columns: number]
) { }
That means the arguments to useNavigation() must either be: a pair where the first element is "horizontal" or "vertical" and the second element is an optional number value; or a pair where the first element is "grid" and the second element is a required number value.
You can test that it works as desired:
useNavigation("horizontal") // okay
useNavigation("vertical", 20); // okay
useNavigation("grid", 10); //okay
useNavigation("grid"); // error!
// ---------> ~~~~~~
// Source has 1 element(s) but target requires 2
Note that the type signature for useNavigation() is also taking advantage of labeled tuple elements, to show that we intend the caller to think of the first argument as having a name of type and the second argument as having a name of columns. Such labels are only relevant when it comes to type hints in IntelliSense and do not affect the types or runtime behavior. In particular, there is no variable named type or columns here; the function implementation sees only an args array:
function useNavigation(...args:
[type: "horizontal" | "vertical", columns?: number] |
[type: "grid", columns: number]
) {
if (args[0] === "grid") {
args[1].toFixed(); // okay
}
}
You could, if you want, destructure args into type and columns variables as in
const [type, columns] = args;
but there is an advantage to leaving it as args; namely, that the compiler sees args as a discriminated union type. Note that in the above implementation, the compiler sees that if args[0] === "grid", then args[1] is definitely a number and not possibly undefined. Compare to the behavior when you separate args out into two no-longer-seen-as-correlated variables:
if (type === "grid") {
columns.toFixed(); // oops, possibly undefined
}
The compiler only sees columns as type number | undefined regardless of whether or not you check type === "grid" first. This might not be a big deal to you, but discriminated unions are useful enough that I wanted to point it out.
Playground link to code