Say we have two types:
Using these types, I'll look at the similarities and differences between:
If we create a new variable and assign it the type Record<Food, Meal>
, it will display an error when a key is not the correct type.
However, because we've assigned the variable to a type which indicates that all values are of type Meal
, and because all possible values of type Meal
are of type string
, then getting the length
of any key is okay. The type checker assumes these values are strings at this point. It's basing this assumption on the assigned type for the variable.
If we expand the type of the values to be Meal | number
, then the value for bread
is now okay:
But now, if we try to get the length
of any key, the type checker says we can't do this. It's basing this off of the assumed type of the variable, and if the value can be a number
, then checking for its length
is an error.
Let's remove number
from the type once again, and now let's try to cast the variable with as
. The type checker sees that the value for bread cannot possibly be Meal
since a number
is not a string
. It doesn't allow this, and it also doesn't provide as much visual specificity in the error. The object cannot possibly match the type, and so the whole object is in error.
The type checker still assumes that the keys are all of type Meal
, nonetheless. It's basing this on the type we've cast the variable as.
We can get around this by first casting the object as unknown
, but this is a bit of a hack. It's like saying, "hey TypeScript, you see this object? Let's not make any assumptions about what type it is. Got it? Okay, now let's cast it as this other type."
Again, no problem checking the length of keys since the type checker is assuming the keys are all strings.
As with type assignment, if we widen the type of the values to include number
, then we can cast the variable as the type without issue.
The type checker does the same thing here when checking for the length of the keys. It's assuming the variable has either strings or numbers for keys, and numbers don't have a length.
One thing to note is that we can reassign bread
to a number, and the type checker does not complain about this. That's because the type checker is assuming the values of the object to be either of type Meal
or number
, so reassigning a value from a string to a number is okay.
Let's once again narrow the type of the values so that they do not include number
. As with type assignment and casting with as
, saying the variable satisfies
the type will show an error. Unlike as
, the visual info here is more specific, and we can see the problem key. In this way, type assignment and satisfies
are a bit better than as
for debugging.
With satisfies
, the type checker does NOT check against all of a value's types. It instead "locks in" the type of the variable at the time of declaration. If the value is a number
, then henceforth all type checking against the value will assume it is a number. If the value starts as a string
, then it will have access to string methods. If it's not a string, then the type checker will throw an error if a string method is used on it.
Let's once again widen the type of the values to include number
. bread
now fits within the type.
But still, the type checker checks against the actual type of the value at creation time.
Interestingly, the type checker will not let you reassign the variable from a string to a number because satisfies
locks in the type of the value at variable creation.