I have this type
interface Route {
name: string,
path: string
}
const routes = [
{
name: "first",
path: "/first"
},
{
name: "second",
path: "/second"
}
] as const
And I would like to create a function that would help me get a specific route path for the matching route.
const to = <Routes extends ReadonlyArray<Route>, CurrentRoute extends Routes[number]>(routes: Routes, name: CurrentRoute["name"]): CurrentRoute["path"] => {
// Implementation irrelevant here
return "" as CurrentRoute["path"]
}
const path = to(routes, "first")
// Does not work, return a union instead of a string
I would like this function to return a single string that is infered from the matching object's name so that:
"/first"
is returned by TypeScript when calling this function with"first"
"/second"
is returned by TypeScript when calling this function with"second"
- ...
The implementation is irrelevant, I would like a TypeScript's type system's solution, the implementation is trivial.
CodePudding user response:
Your version doesn't work because there is no inference site for the CurrentRoute
generic type argument. For better or worse, generic types cannot be inferred from indexed access types like CurrentRoute["name"]
; there was an old pull request at microsoft/TypeScript#20126 which would have done this, but it never made it into the language.
To make this work you should have the name
property correspond directly to a generic type parameter (let's call it N
), and then use that type to compute the type corresponding to your CurrentRoute
. Here's one way to do it:
const to = <R extends Route, N extends R["name"]>(
routes: readonly R[], name: N): Extract<R, { name: N }>["path"] => {
// Implementation irrelevant here
return null!;
}
Because we don't care about the exact order of the elements of routes
(at least not in your example), I have changed your Routes
type parameter (corresponding to the entire array) to just R
(corresponding to the union of members of that array). The N
type parameter is constrained to R["name"]
, meaning the union of name
property types in R
.
And the return type takes R
and uses the Extract<T, U>
utility type to extract the union member with a name
property of type N
, and then indexes into that to get the path
property type.
Let's make sure it works:
const path = to(routes, "first");
// const path: "/first"
const path2 = to(routes, "second");
// const path2: "/second"
Looks good!