Item 62: Use Rest Parameters and Tuple Types to Model Variadic Functions
May 10, 2024 ยท View on GitHub
Things to Remember
- Use rest parameters and tuple types to model functions whose signature depends on the type of an argument.
- Use conditional types to model relationships between the type of one parameter and the number and type of the remaining parameters.
- Remember to label the elements of your tuple types to get meaningful parameter names at call sites.
Code Samples
interface RouteQueryParams {
'/': null,
'/search': { query: string; language?: string; }
// ...
}
function buildURL(route: keyof RouteQueryParams, params?: any) {
return route + (params ? `?${new URLSearchParams(params)}` : '');
}
console.log(buildURL('/search', {query: 'do a barrel roll', language: 'en'}))
console.log(buildURL('/'))
buildURL('/', {query: 'recursion'}); // should be an error (no params for root)
buildURL('/search'); // should be an error (missing params)
function buildURL<Path extends keyof RouteQueryParams>(
route: Path,
params: RouteQueryParams[Path]
) {
return route + (params ? `?${new URLSearchParams(params)}` : '');
}
buildURL('/search', {query: 'do a barrel roll'})
buildURL('/search', {query: 'do a barrel roll', language: 'en'})
buildURL('/search', {})
// ~~ Property 'query' is missing in type '{}'
buildURL('/', {query: 'recursion'}); // error, good!
// ~~~~~~~~~~~~~~~~~~~~ Argument of type '{ query: string; }' is
// not assignable to parameter of type 'null'
buildURL('/', null); // ok
buildURL('/'); // we'd like this to be allowed
// ~~~~~ Expected 2 arguments, but got 1.
function buildURL<Path extends keyof RouteQueryParams>(
route: Path,
...args: (
RouteQueryParams[Path] extends null
? []
: [params: RouteQueryParams[Path]]
)
) {
const params = args ? args[0] : null;
return route + (params ? `?${new URLSearchParams(params)}` : '');
}
buildURL('/search', {query: 'do a barrel roll'})
buildURL('/search', {query: 'do a barrel roll', language: 'en'})
buildURL('/search', {})
// ~~ Property 'query' is missing in type '{}' ...
buildURL('/', {query: 'recursion'});
// ~~~~~~~~~~~~~~~~~~~~ Expected 1 arguments, but got 2.
buildURL('/', null);
// ~~~~ Expected 1 arguments, but got 2.
buildURL('/'); // ok
buildURL('/');
// ^? function buildURL<"/">(route: "/"): string
buildURL('/search', {query: 'do a barrel roll'})
// ^? function buildURL<"/search">(
// route: "/search", params: { query: string; language?: string; }
// ): string