Reputation: 3758
Say, I have a structure like
//Modified from https://blog.parametricstudios.com/posts/pattern-matching-custom-data-types/.
enum RouteEnum
{
Home = "/",
Todos = "/todos",
Todo = "/todos/:id",
NotFound = "*"
}
class HomeLocation
{
readonly route = RouteEnum.Home;
match<Out>(matcher: LocationMatcher<Out>): Out
{
return matcher[RouteEnum.Home](this);
}
}
// A list of all Todo item links, when one is linked the next route is in TodoLocation.
class TodosLocation
{
readonly route = RouteEnum.Todos;
match<Out>(matcher: LocationMatcher<Out>): Out
{
return matcher[RouteEnum.Todos](this);
}
}
// A single Todo location.
class TodoLocation
{
readonly route = RouteEnum.Todo;
match<Out>(matcher: LocationMatcher<Out>): Out
{
return matcher[RouteEnum.Todo](this);
}
}
class NotFoundLocation
{
readonly route = RouteEnum.NotFound;
match<Out>(matcher: LocationMatcher<Out>): Out
{
return matcher[RouteEnum.NotFound](this);
}
}
type NewLocation = HomeLocation | TodosLocation | TodoLocation | NotFoundLocation;
type LocationMatcher<Out> =
{
[RouteEnum.Home]: (route: HomeLocation) => Out;
[RouteEnum.Todos]: (route: TodosLocation) => Out;
[RouteEnum.Todo]: (route: TodoLocation) => Out;
[RouteEnum.NotFound]: (route: NotFoundLocation) => Out;
};
would it be possible to turn NewLocation
into a structure like
// The matching order here is from the the first to latter.
const routes = {
'/': HomeLocation,
'/todos': TodosLocation,
'/todos/:id': TodoLocation,
'/*': NotFoundLocation
}
In case it matters, the larger reason for this question is that I'm thinking to do route matching over this structure but naturally there needs to be a way to turn URLs to those *Location
classes.
One example could be using something like https://github.com/CaptainCodeman/js-router (or a similar approach that more readily fits this structure). This particular library for instance operates on a structure like that routes
example. I think that even without using this specific library knowing how to enumerate over the discriminated union and create a new type would be good to know information.
The goal would be to have the parsed parameters as strongly typed. This is manual, of course, but they need to matched first from their corresponding route in the *Location
classes, in one way or another. One option is to have a callback function per *Location
class that takes the input from a matched route.
Edit I wrote a StackBlitz to illustrate my pondering better. It's at https://stackblitz.com/edit/typescript-hl6drb?file=index.ts.
Upvotes: 1
Views: 367
Reputation: 327944
If you want to programmatically generate an object type where the keys come from the route
property of the NewLocation
union members and where the values are constructors for the corresponding element of NewLocation
, then you can do it like this:
type RoutesType = {
[K in NewLocation['route']]: new () => Extract<NewLocation, { route: K }>
};
That evaluates to the equivalent of
type RoutesType = {
"/": new () => HomeLocation;
"/todos": new () => TodosLocation;
"/todos/:id": new () => TodoLocation;
"*": new () => NotFoundLocation;
}
and you can then annotate the routes
variable as that type:
const routes: RoutesType = {
'/': HomeLocation,
'/todos': TodosLocation,
'/todos/:id': TodoLocation,
'*': NotFoundLocation
}
note that this is the same thing as
const routes: RoutesType = {
[RouteEnum.Home]: HomeLocation,
[RouteEnum.Todos]: TodosLocation,
[RouteEnum.Todo]: TodoLocation,
[RouteEnum.NotFound]: NotFoundLocation
}
so you can write that either way if you want.
Does that work for you? Hope that helps; good luck!
There's also something like this which is the closest I can get to going from values to types here...
Upvotes: 2