Everyday TypeScript: Unknown
Welcome to the Unknown lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
Sometimes we can't know a variable's type in advance. Here's one such situation:
Suppose that we're writing a library in TypeScript, but it will be published to NPM for public consumption. Some users will call our library from their JavaScript applications, where there's no type system present. When they call our exported functions, our types won't be enforced.
Maybe we have a function that takes a
numberargument. Hopefully, the user will pass anumberto it, but they're not using TypeScript so they might pass astring. We'd like to tell TypeScript something like "we're not sure about this argument's true type, so please force us to check the value before moving on." We want TypeScript to force us to narrow the type usingArray.isArray, ortypeof x === 'number', or some other check.As of TypeScript 3.0, we can do that with the
unknowntype. It means exactly what was described above: "we don't know what type this variable holds, so don't let us do anything with it until we find out."We can assign any value to an
unknown, so it's likeanyin that respect.(These examples return
undefined, because that's what a variable declaration statement evaluates to in JavaScript. The fact that they returnundefinedrather than giving a type error shows us that they compiled.)>
const n: unknown = 5;Result:
>
const s: unknown = 'five';Result:
Unlike
any, we can't useunknownin a place where TypeScript expects astring, anumber, or (almost) any other type. Any attempt to do that is a type error. (You can answer these examples withtype error.)>
const n: unknown = 5;const n2: number = n;n2;Result:
type error: Type 'unknown' is not assignable to type 'number'.
>
const s: unknown = 'five';const s2: string = s;Result:
type error: Type 'unknown' is not assignable to type 'string'.
Those examples look a bit strange because we can see that
nis a number andsis a string. But the compiler only cares about the types, and we explicitly told it thatnandshave the typeunknown.What good is a variable if you can't read from it? To make
unknownuseful, we have to narrow the type back to a real, concrete type. For example, we can use conditional narrowing to narrow theunknownback to astring. Then we're back in the world of static types with a normalstring, and all of the normal rules apply again.However, narrowing will force us to handle the case where the
unknownvalue isn't the type that we wanted. We can handle that in any way we like: throw a runtime error, use a default value, etc. In the examples below, we'll use a default value.>
const s: unknown = 'a string';const s2: string = typeof s === 'string' ? s : '';s2;Result:
'a string'
>
const s: unknown = 5;const s2: string = typeof s === 'string' ? s : '';s2;Result:
''
>
const n: unknown = 5;const n2: number = typeof n === 'number' ? n : 0;n2;Result:
5
>
const n: unknown = 'a string';const n2: number = typeof n === 'number' ? n : 0;n2;Result:
0
Let's compare
anytounknownby writing an example for each.Here's a code problem:
Use
anyto make this code compile, even though it's wrong. (It's assigning anumbervalue to astring.)const n: any = 5;const s: string = n;s;- Goal:
5
- Yours:
5
Here's our "author's answer" from the code problem above, except that now we use
unknowninstead ofany. Everything else is the same. Now we get a type error, showing our mistake.>
const n: unknown = 5;const s: string = n;s;Result:
type error: Type 'unknown' is not assignable to type 'string'.
anyandunknownare assignable to each other. This makes sense, because both mean "we don't know the type.">
const n: unknown = 5;const n2: any = n;n2;Result:
5
>
const n: unknown = 5;const n2: any = n;const n3: number = n2;n3;Result:
5
Note that
anyis still unsafe! As soon as we have a variable of typeany, we can assign it to anything. The next example makes an intentional type error that TypeScript can't detect because we used anany.>
const x1: unknown = 'five';const x2: any = x1;const x3: number = x2;x3;Result:
'five'
We can also do the same thing in reverse. If we get an
anyfrom a JavaScript library, we can immediately put it into anunknown. That will stop us from accidentally assigning it directly to a concrete type that might be incorrect.>
const n: any = 'five';const n2: unknown = n;const n3: number = n2;Result:
type error: Type 'unknown' is not assignable to type 'number'.
>
const n: any = 'five';const n2: unknown = n;const n3: number = typeof n2 === 'number' ? n2 : 0;n3;Result:
0
When we union
unknownwith another type, liketype StringOrUnknown = unknown | string, the entire union is equivalent to justunknown. Theunknown"absorbs" thestring. Think of it this way: If the TypeScript compiler doesn't know the type of a variable, and we add in one more option, the compiler still doesn't know the type.(
unknown | anyevaluates toanyfor the same reason. There's no practical reason to write that type, though.)>
const s: unknown | string = 'a string';const s2: string = s;s2;Result:
type error: Type 'unknown' is not assignable to type 'string'.
When we intersect
unknownwith another type, liketype UnknownIntersection = unknown & string, the other type absorbs theunknown. Think of it this way: If we don't know what type something is, but we also know that it's a string, then we actually know that it's just a string.>
const s: unknown & string = 'a string';const s2: string = s;s2;Result:
'a string'
Finally, a note on terminology.
anymeans "a variable of any type, assignable to anything". The nameanyis very appropriate; it applies to both reading and writing.unknownmeans "we don't know what's in this variable, so we can't use it until we find out".For interoperability with existing JavaScript libraries, you'll sometimes find yourself using
any. But in your own TypeScript code, we recommend usingunknownto represent values whose type isn't known yet. Then the compiler will help you by forcing you to safely narrow them without losing the benefits of the type checker.