The unknown
mistery
When I first onboarded TypeScript world from JavaScript, one of the most confusing thing is the unknown
type. Remember when your coworkers write something like:
const foo = () => bar as unknown as any // <- 🤷🏻♂️🤷🏻♂️🤷🏻♂️
Let’s take a look at TypeScript official documentation:
unknown
is the type-safe counterpart of anyAnything is assignable to
unknown
, butunknown
is not assignable to anything but itself and any without a type assertion or a control flow based narrowing.
Does this clarify or just further confuse you? I know we don’t like boring texts and documentation, so let’s translate those into actual code:
Notice that even after being assigned to a number-typed value, foo
still has unknown type and thus cannot do number-specific stuffs:
const foo: unknown = 1
console.log(foo > 10) // <- ⛔️⛔️⛔️ foo is still unknown, so it cannot be compared to a number
You can however make the compiler happy by casting the type of `foo` to number like:
// Rest assure compiler, I know what I am doing with foo
console.log(foo as number > 10)
In this trivial example, there is not much risk because we already assigned foo = 1
in the first place. However, in more complex cases, casting the type like this might backfire us:
Alright that’s enough for the basics, now let me show you the most common real-world usages of the unknown
type and how to safely handle it 💪💪💪
Handle external data sources
We need to be super careful getting and parsing these data sources such as 3rd party JSON responses.
An analogy for this is when you received a “gift” from some strangers. You almost always want to check it carefully before actually using.
Let’s say we want to fetch a dog data by its id from a 3rd party API:
const dog = await fetchDogByIdFromApi(1)
Could we know what type this dog
have? 🤔 Most likely NOT before we could do some checks.
So what should we do? We should consider it to have unknown
type ❓. By doing this, TypeScript will prevent us from freely assigning its value such as:
The compiler also does not allow us to carelessly read the data like:
const ourDogName = fetchedDog.name.toLowerCase() // 💣⛔️⛔️ <- this will explode if fetchedDog.name does not exist
Pretty useful isn’t it? You must have additional checks to be able to assign an unknown
value to our known Dog
-typed variable. This totally make sense. Otherwise what would be the benefits of TypeScript if we just freely assign a not-yet-known value to a variable? 😅 This is probably 1 of the most common source for the timeless error: TypeError: Cannot Read Property of Undefined.
So how could we safely handle unknown
type and let TypeScript be at ease? Here are 2 approaches that I often go to: typeguards and type assertions.
Typeguards
Typeguard is an expression — an additional check to assure TypeScript that an unknown
value conforms to a type definition.
Let’s see how can we write a typeguard to safely use fetchedDog
value as our own Dog
:
In this typeguard, we are using type predicate — a special return types that tells TypeScript compiler that as long as value
satisfies inner checks, it must have Dog
type.
Note that it is safe to use typecast value as Dog
in this case to access name
property because we already checked type value === 'object' && 'name' in value
With this in place, we can totally treat the newbie fetchedDog
as 1 of our own:
Type assertions
An alternative to typeguard is an assertion function to throw an error if the input does not conform to our Dog
type:
This assertDog
function does basically what isDog
typeguard did earlier. The only difference is the signature where it throw an Error
right away if the type is mismatched. Therefore, we don’t need an if/else
statement in the calling code:
Key takeaways
unknown
is a pretty great feature of TypeScript in my opinion. It proves extremely useful when we need to parse, well unknown, 3rd party data sources.
To summary what we have gone through in this blog:
- Anything is assignable to
unknown
, butunknown
is not assignable to anything but itself. So we can doconst foo: unknown = 'foo'
, but we cannot do, out of the box,const bar: string = foo
iffoo
isunknown
. - We can force the compiler to trust that an unknown varible has a specific type:
const bar: string = foo as string
. However, typecast might backfire us if we are not mindful using it. - Typeguards or type assertions are really powerful expressions that perform runtime checks to guarantee the type of an
unknown
value. They are generally better and safer to use that typecasts.
That’s the end of this blog. I would love to hear your ideas and thoughts 🤗 Please jot them down bellow 👇👇👇
✍️ Written by
Vinh Le @vinhle95
👨🏻💻🤓🏋️🏸🎾🚀