Typescript 4.5 was just released and one of the features that stood out to me is the Template String Types as Discriminants. In this article, we are going to explore this new feature using rudimentary examples. In my last article, we covered using discriminated unions to write better types that are easy to narrow.
This is an extension of that but instead of having a concrete literal type, you can use a non-concrete literal type i.e. string, number, etc instead as part of the template literal type, and Typescript will be able to use it as a discriminant.
In order to understand this feature, we are going to start by creating two types: SuccessType
and ErrorType
. They are going to represent possible responses for different operations we can perform in a computer system i.e. HTTP Request, FTP Request, IO Request, etc. So, if an HTTP request succeeds we get a SuccessType
data, if it fails we get an ErrorType
data.
For the two types, each will have a type
property, which we can use to discriminate between the two types when they are used in a union i.e. ResponseType
union. But Instead of using a concrete literal type, we will use a template string type instead.
This means that the resulting template literal type could be any string combined with Success
or Error
i.e. ${string}Success
and ${string}Error
. This will allow our success type to cover a number of possible operations like httpSuccess
, ftpSuccess
, etc. and the same goes for ErrorType
.
type SuccessType = {
type: `${string}Success`,
data: Record<string, unknown>;
}
type ErrorType = {
type: `${string}Error`,
message: string;
}
type ResponseType = SuccessType | ErrorType;
function processHTTPResponse(response: ResponseType) {
// function body here
}
In previous versions, Typescript won't be able to narrow down the type of the ResponseType
union based on the type field, as shown below.
But as of the latest version (4.5 and above), typescript is able to narrow the type of response
to SuccessType
as shown below.
As you can imagine, this opens up a world of new possibilities by providing a literal type that is not concrete, typescript can discriminate between two unions as long as the field used to discriminate is contained in the string being compared to. Here is another rudimentary example:
type HttpOK = {
status: `2${string}`;
data: string;
}
type Http500 = {
status: `5${number}`;
message: string;
}
type Http300 = {
status: `3${string}`;
redirect: string;
}
function processResponse(response: HttpOK | Http300 | Http500) {
if(response.status === "200") {
console.log(response.data);
}
if(response.status === "300") {
console.log(response.redirect);
}
if(response.status === "500") {
console.log(response.message);
}
}
Here is a link to Typescript Playground for the above code.
Conclusion
In this brief article, we looked at a new feature coming to Typescript v4.5 for using Template String Types as a discriminant. This allows us to build more versatile types by relying on a template pattern for the discriminant property rather than an exact string.