I have an object like this:
const routes = {
home: { path: '/', page: 'home' },
profile: { path: '/profile', page: 'users/profile' }
}
I would like to define a derived type from this, like so:
type RouteName = keyof typeof routes
, which creates a type like "home" | "profile"
.
However, I can't then do:
for (let name in routes) {
router.add({ name, ...routes[name]})
}
because the compiler complains about routes[name]
being of implicit type any
:
Element implicitly has an 'any' type because type '{ home: { path: string; page: string; }; profile: { path: string; page: string; };' has no index signature.
If I modify the definition of routes to:
interface RouteDefinition {
path: string
page: string
}
const routes: {[key: string]: RouteDefinition} = {
home: { path: '/', page: 'home' },
profile: { path: '/profile', page: 'users/profile' }
}
the generated type type RouteName = keyof typeof routes
is now string
instead of "home"|"profile"
.
I could of course define a hardcoded RouteName
type, but in case it isn't clear, I'm trying to avoid having to define the route names in two places, especially when the keys of the object strictly define the set of possibilities.
The object only needs to be defined once, and never needs to be reassigned. I've tried a bunch of combinations of Readonly<>
, casting etc, but can't figure it out. Is there a way to do this?
(I'm using Typescript 2.8.1)
Use the keyof typeof syntax to create a type from an object's keys, e.g. type Keys = keyof typeof person . The keyof typeof syntax returns a type that represents all of the object's keys as strings.
Use Map type and new keyword to create a map in TypeScript. let myMap = new Map<string, number>(); To create a Map with initial key-value pairs, pass the key-value pairs as an array to the Map constructor.
TypeScript doesn't consider it safe to assume that for..in
keys are exactly the keys defined in the type, because in JavaScript all objects are open.
You can use an assertion to make the compile error go away:
for (let name in routes) {
routes[name as RouteName]; // no error
}
Or, what I would do is combine your two approaches. You can define your routes
as you are doing, extract the keys as a RouteName
, but also make a RouteDefinition
and assign your routes to an indexed type (which could be done using an assertion to a new variable, or a function parameter) when you want to map over them:
interface RouteDefinition {
path: string;
page: string;
}
const routes = {
home: { path: '/', page: 'home' },
profile: { path: '/profile', page: 'users/profile' }
}
type RouteName = keyof typeof routes;
mapRoutes(routes);
function mapRoutes(routes: { [key: string]: RouteDefinition }) {
for (let name in routes) {
routes[name] // no error
}
}
If your routes
literal doesn't satisfy the RouteDefinition
(missing a key, key of wrong type) then you'll get an error at the assignment site, ie mapRoutes(routes)
above.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With