Reputation: 960
My issue AsValue
utility type feature request in TypeScript repository has been declined and Nov 2020. I expected the below API:
const MarkupPreprocessingDefaultSettings: AsValue = {
mustExecuteCodeQualityInspection: true,
waitingForOtherFilesWillBeSavedPeriod__milliseconds: 1000,
entryPointsGroupDependent: {
indentationSpacesCountInOutputCode: 2,
mustExecuteHTML5_Validation: true,
mustExecuteAccessibilityInspection: true
}
};
// Same as
const MarkupPreprocessingDefaultSettings: {
mustExecuteCodeQualityInspection: true,
waitingForOtherFilesWillBeSavedPeriod__milliseconds: 1000,
entryPointsGroupDependent: {
indentationSpacesCountInOutputCode: 2,
mustExecuteHTML5_Validation: true,
mustExecuteAccessibilityInspection: true
}
} = {
mustExecuteCodeQualityInspection: true,
waitingForOtherFilesWillBeSavedPeriod__milliseconds: 1000,
entryPointsGroupDependent: {
indentationSpacesCountInOutputCode: 2,
mustExecuteHTML5_Validation: true,
mustExecuteAccessibilityInspection: true
}
};
O'K, but what we have instead in reality? Let's consider the concrete problem.
Annotate the routing
variable such as:
routing.products
must work, but routing.gibberish
must cast the TypeScript error.products
and orders
could be arbitrary large.any
and object
allowed.type Route = {
URN: string;
}
const routing /* : ??? */ = {
products: {
URN: "/products"
},
orders: {
URN: "/orders"
}
}
const routing : {
products: Route;
orders: Route;
// and several thousands of keys for real commercial application ...
} = {
products: {
URN: "/products"
},
orders: {
URN: "/orders"
},
// and several thousands of keys for real commercial application ...
}
Conclusion: non-technological, hard to extend and maintenance. Unacceptable.
type Route = {
URN: string;
}
const Routing: { [routeName: string]: Route } = {
products: {
URN: "/products"
},
orders: {
URN: "/orders"
}
}
console.log(Routing.products)
console.log(Routing.orders)
console.log(Routing.gibberish) // Undefined!
Conclusion: it will not prevent the typo; no autocomplete available. Unacceptable.
Upvotes: 1
Views: 151
Reputation: 330436
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'
}
})
Upvotes: 2