I have a generic type called RouteConfig which I am trying to use inside of an object called routes.
// Generic type
type RouteConfig<Params> = {
path: string;
component: (params: Params) => string;
};
type Route = 'Home' | 'User';
// Object
const routes: Record<Route, RouteConfig<unknown>> = {
Home: {
path: 'a',
// False positive (should not error)
component: (params: { foo: string }) => 'x',
},
User: {
path: 'a',
// False positive (should not error)
component: (params: { bar: string }) => 'z',
},
};
Each value inside of the object is allowed to have its own type for the Params generic inside of RouteConfig.
My problem is this: inside of the routes type annotation, what should I pass as the generic to RouteConfig?
I can't provide a single type since each object value is allowed to have its own type. (A union would apply the same union type to all object values, which is not what I want.)
In the example above I am using unknown, however this results in false positive type errors (see comments in code example above).
I can't use any because then I lose type safety when it comes to reading from the object:
const routes: Record<Route, RouteConfig<any>> = {
Home: {
path: 'a',
// True negative
component: (params: { foo: string }) => 'x',
},
User: {
path: 'a',
// True negative
component: (params: { bar: string }) => 'z',
},
};
// True negative
routes.Home.component({ foo: 'abc' });
// False negative (should error)
routes.Home.component({ bar: 'abc' });
I could drop the type annotation from routes:
const routes = {
Home: {
path: 'a',
// True negative
component: (params: { foo: string }) => 'x',
},
User: {
path: 'a',
// True negative
component: (params: { bar: string }) => 'z',
},
};
// True negative
routes.Home.component({ foo: 'abc' });
// True positive
routes.Home.component({ bar: 'abc' });
… but then I lose facilities like "find references" and "rename" for the type and properties inside of RouteConfig. Furthermore, I would lose some type safety because TypeScript would no longer be able to check that the object contains all of the required keys (defined by the Route type).
I think what I'm looking for is a way to annotate the type for the routes object except for the generic—the generic should be inferred from the object definition.
To sum up, I am looking for a way to write this that achieves all of the following:
- Good dev UX:
- if I rename a property in
RouteConfig, does the change apply to all usages?
- if I rename a property in
- can I find all references for a property in
RouteConfig?
- can I find all references for a property in
- No unexpected errors
- Type safety (errors when expected)
Is there any way I can achieve all of the above?