Reputation: 1155
I have a type
type DiceNumber = 1 | 2 | 3 | 4 | 5 | 6
for part of a react state. I want to set the state to a random number between 1 and 6, so I try this
const dice = Math.floor(Math.random() * 6) + 1;
but I get
Type 'number' is not assignable to type 'DiceNumber'
.
I understand why, but is there a way around this? Can I guarantee random numbers to fit the type?
Upvotes: 1
Views: 852
Reputation: 328387
The type system in TypeScript doesn't know how to represent the outcome of mathematical operations on numeric literal types. If you start taking numeric literals like 6
or 1
and do math with them, the compiler will widen the answer to number
.
There are some issues in GitHub that, if implemented, would help here. There's microsoft/TypeScript#26382, asking for arithmetic like *
and +
to be reflected in the type system. There's microsoft/TypeScript#15480, asking for numeric ranges as types, which could possibly represent the fact that Math.random()
outputs numbers between 0
(inclusive) and 1
(exclusive). And there's microsoft/TypeScript#4639, asking for integer types, which could possibly make sense of what Math.floor()
does. All of these are fairly old issues and it's not clear when or if they will ever become features. For now, the type system sees Math.floor(Math.random() * 6) + 1
as just a number
. You know it will be of type DiceNumber
, but the compiler doesn't.
It's in situations like this, where you know something about the type of a value that the compiler doesn't, where a type assertion is useful. You can just tell the compiler that the expression is of type DiceNumber
:
const dice = (Math.floor(Math.random() * 6) + 1) as DiceNumber; // okay
This works, and you can move on with treating dice
like a union of six numeric literals. Of course, type assertions are easily abused, since you can easily lie to the compiler:
const brokenDice = 7 as DiceNumber; // whoopsie
so type assertions are best used judiciously. When you use a type assertion, you're taking some responsibility for type safety away from the compiler, and taking on that responsibility yourself. So be careful!
Anyway, I hope that helps. I'm not 100% sure you will find DiceNumber
a very useful type, since the fact that the compiler can't do math with numeric literals means that 1 | 2 | 3 | 4 | 5 | 6
can only really be used with things like switch
/case
statements or if
/else
statements that check for particular values. There's no built-in way to, say, add two DiceNumber
values and have the compiler see that you now have a value of type 2 | 3 | ... | 11 | 12
. So you might find yourself constantly using type assertions to narrow types from number
back to your custom numeric literal unions. At which point it's not really helping you all that much. But that's up to you and your use case, I guess.
Good luck!
Upvotes: 1