I have created a custom route type for my project url. The function accepts two parameters,
My second parameter (param) in the function can be undefined if there is no query path like project url in the code. But I find it difficult to add optional type when param type is undefiend. Anyone has better solution?
const projectDetailUrl = "/project/:id";
const projectUrl = "/project";
export type ExtractParams<T extends string> =
T extends `${infer _Start}:${infer Param}/${infer _End}`
? Param | ExtractParams<_End>
: T extends `${infer _Start}:${infer LastParam}`
? LastParam
: never;
export type CreateParamObject<T extends string> = ExtractParams<T> extends
| never
| undefined
? undefined
: {
[K in ExtractParams<T>]: string;
};
function push<T extends string>(url: T, params: CreateParamObject<T>) {
console.log(url)
console.log(params)
}
push(projectUrl,undefined)
push(projectDetailUrl,{id:"1"})
I want to removed undefiend without an error but keep params type if there is query path
AS IS
push(projectUrl,undefined)
push(projectDetailUrl,{id:"1"})
TO BE
push(projectUrl)
push(projectDetailUrl,{id:"1"})
You can do that with generics and a conditional type using rest parameters.
First infer the parameters of the URL with a generic parameter and your type ExtractParams
. Since that type either returns a union of string
s or never
we can use a conditional type on it to check the result. If [ExtractParams<T>] extends [never]
we know there are no params and return [{ [x: string]: string }?]
to represent an optional object with string
keys and string
values. Otherwise we return the computed parameter object with [CreateParamObject<T>]
.
See Optional parameters based on conditional types and microsoft/TypeScript/pull/24897 for more information about tuples in rest parameters and spread expressions.
const projectDetailUrl = "/project/:id";
const projectUrl = "/project";
export type ExtractParams<T extends string> =
T extends `${infer _Start}:${infer Param}/${infer _End}`
? Param | ExtractParams<_End>
: T extends `${infer _Start}:${infer LastParam}`
? LastParam
: never;
export type CreateParamObject<T extends string> = {
[K in ExtractParams<T>]: string;
};
function push<T extends string, U extends ExtractParams<T>>(
url: T,
...params: [U] extends [never]
? [{ [x: string]: string }?]
: [CreateParamObject<T>]
) {
console.log(url);
console.log(params);
}
push(projectUrl); // okay
push(projectUrl, { whatever: "1" }); // okay
push(projectDetailUrl, { id: "1" }); // okay
push(projectDetailUrl); // error