Option 3: Use a generic helper function
const asRouting = <T extends Record<keyof T, Route>>(t: T) => t;
const routing = asRouting({
products: {
URN: "/products"
},
orders: {
URN: "/orders"
}
});
If you annotate the routing
variable at all, the compiler will treat the variable as being of the annotated type only, and you won't get any type inference. As you see, either you have to annotate with exactly the right type, which is redundant, or you annotate a type which is wider than you want, and you lose type information you care about. So you really shouldn't annotate routing
. Let the compiler infer its type for you.
But what if you mistakenly initialize routing
with a value where one or more of the properties is not a valid Route
? Well, the code that uses routing
will yield a compiler error. But that is likely to be quite far from the definition of routing
. You'd like the compiler error to be right there so you can fix it.
That's where the generic helper function above comes in. asRouting()
is an identity function at runtime, and just outputs the same value as the input. At compile time, it is also an identity function, and the output type will be the same as the input type. But since this type, T
, is constrained to Record<keyof T, Route>
, asRouting()
will only accept inputs where all its properties are valid Route
objects.
Let's see what happens with routing
as defined above. Its inferred type can be shown via IntelliSense to be:
/* const routing: {
products: {
URN: string;
};
orders: {
URN: string;
};
} */
Which is exactly the type you want, without requiring that you spell it out:
console.log(routing.products.URN); // okay
console.log(routing.orders.URN); // okay
console.log(routing.somethingElse); // compiler error!
// ---------------> ~~~~~~~~~~~~~
// Property 'somethingElse' does not exist
But you still get all the type checking you need to ensure that all properties are Route
:
const badRouting = asRouting({
products: {
URL: "/product"; // error!
// ~~~~~~~~~~~~~~~~
// Object literal may only specify known properties,
// and 'URL' does not exist in type 'Route'
}
})
Playground link to code
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…