Imagine you have a function like this one:
interface Post {
id: number
title: string
}
export function getPosts(page: number): Post[] {
// perform some query to return posts from a database
// ...
// ...
return []
}
getPosts
accepts a page
argument and retrieves posts from a data source.
Now, what if getPosts
is used like this?
const posts = getPosts(-1);
Obviously That's incorrect because a page number can not be negative. A simple solution would be add a line like this one:
if(page < 0) throw Error('Page must be a positive number')`
But would not be nice if we receive a type error when we try to call the function with a negative number?
The problem here, is that the type number
is too general for our function. number
can be any number and we are expecting just a positive number.
We can create a type alias for it:
type PositiveNumber = number;
Although it makes our code more readable, using a simple alias doesn't solve the problem. Because we are still able to pass any number as an argument to getPosts()
.
This is when we can create a Branded Type.
Branded Types
When the base type is too general we can use this pattern.
type PositiveNumber = number & { __type: 'PositiveNumber' };
Here we are using an intersection between the base type (number
) and an object with the __type
property, this private property holds the brand type, and its used to differentiate it from a regular number
.
We can rewrite our function to:
type PositiveNumber = number & { __type: 'PositiveNumber' };
export function getPosts(page: PositiveNumber): Post[] {
// ...
return []
}
Now if we try to call it like before, we're going to receive the error we expect.
const posts = getPosts(-1);
// ^^^ Argument of type 'number' is not assignable to parameter of type 'PositiveNumber'.
Let's try with a valid number:
const posts = getPosts(2);
// ^^^ Argument of type 'number' is not assignable to parameter of type 'PositiveNumber'.
Hmm, that's doesn't make sense 🤔, I'm sure 2
is a positive number.
We are still receiving the same error because we are still passing a number
, not a PositiveNumber
. Son wee need to explicitly say to the compiler that we are passing a PositiveNumber
using the as
keyword.
const posts = getPosts(2 as PositiveNumber);
That will get rid of the error.
Now, what if we have a variable that we can't tell if it's a positive number or not?
const { page } = incomingRequest.body;
// here, page could be a positive or a negative number, so we can't use the `as` keyword because we can't be sure of what is the actual value;
const posts = getPosts(page);
To solve this we can use an assertion function that asserts page is an actual PositiveNumber
function assertsPositiveNumber(value: number): asserts value is PositiveNumber {
if(value < 0) throw new Error('Value must be a positive number');
}
Now with this assertion, we can tell if page
is a positive number or not.
const { page } = incomingRequest.body;
assertsPositiveNumber(page);
const posts = getPosts(page); // at this point TypeScript know that `page` its a PositiveNumber
If you liked this tip, please, share it and hit the ♥
Top comments (1)
Thanks, this is exactly what I was looking for!