Any and unknown types in TypeScript
There are times when you'll need to work with values that are unknown to you at the time you develop your code. In these cases, you can use the any
and unknown
types and use type assertion and type guards to maintain control over what your code is allowed to do with the values that are passed.
Any type
The any
type is the one type that can represent any JavaScript value with no constraints. This type can be useful when you're expecting a value from a third-party library or user inputs where the value is dynamic because the any
type will allow you to reassign different types of values. And, as mentioned earlier, using the any
type allows you to gradually migrate your JavaScript code to use static types in TypeScript.
The following example declares a variable of type any
and assigns values to it:
let randomValue: any = 10;
randomValue = 'Mateo'; // OK
randomValue = true; // OK
When this example is compiled, it doesn't throw an error because the any
type encompasses values of every possible type. The any
type opts out of type checking and doesn't force you to do any checking before you call, construct, or access properties on these values.
Using the any
type in this example allows you to call:
- A property that doesn't exist for the type.
randomValue
as a function.- A method that only applies to a
string
type.
Because randomValue
is registered as any
, all of the following examples are valid TypeScript and will not generate a compile-time error. However, runtime errors may occur depending on the actual datatype of the variable. Given the previous example where randomValue
is set to a Boolean value, the following lines of code will generate issues at runtime:
console.log(randomValue.name); // Logs "undefined" to the console
randomValue(); // Returns "randomValue is not a function" error
randomValue.toUpperCase(); // Returns "randomValue is not a function" error
Important
Remember that all the convenience of any
comes at the cost of losing type safety. Type safety is one of the main motivations for using TypeScript. You should avoid using any
when it's not necessary.
Unknown type
While flexible, the any
type can cause unexpected errors. To address this issue, TypeScript introduced the unknown
type.
The unknown
type is similar to the any
type in that any value is assignable to type unknown
. However, you can't access any properties of an unknown
type, nor can you call or construct them.
This example changes the any
type in the previous example to unknown
. It will now raise type check errors and prevent you from compiling the code until you take appropriate action to resolve them.
let randomValue: unknown = 10;
randomValue = true;
randomValue = 'Mateo';
console.log(randomValue.name); // Error: Object is of type unknown
randomValue(); // Error: Object is of type unknown
randomValue.toUpperCase(); // Error: Object is of type unknown
Note
The core difference between any
and unknown
is you are unable to interact with a variable of type unknown
; doing so generates a compiler error. any
bypasses any compile-time checks, and the object is evaluated at runtime; if the method or property exists it will behave as expected.
Type assertion
If you need to treat a variable as a different data type, you can use a type assertion. A type assertion tells TypeScript you've performed any special checks that you need before calling the statement. It tells the compiler "trust me, I know what I’m doing." A type assertion is like a type cast in other languages, but it performs no special checking or restructuring of data. It has no runtime effect and is used purely by the compiler.
Type assertions have two forms. One is the as
-syntax:
(randomValue as string).toUpperCase();
The other version is the "angle-bracket" syntax:
(<string>randomValue).toUpperCase();
Note
as
is the preferred syntax. Some applications of TypeScript, such as JSX, can get confused when using < >
for type conversions.
The following example performs the necessary check to determine that randomValue
is a string
before using type assertion to call the toUpperCase
method.
let randomValue: unknown = 10;
randomValue = true;
randomValue = 'Mateo';
if (typeof randomValue === "string") {
console.log((randomValue as string).toUpperCase()); //* Returns MATEO to the console.
} else {
console.log("Error - A string was expected here."); //* Returns an error message.
}
TypeScript now assumes that you have made the necessary check. The type assertion says that randomValue
should be treated as a string
and then the toUpperCase
method can be applied.
Type guards
The previous example demonstrates the use of typeof
in the if
block to examine the type of an expression at runtime. This conditional test is called a type guard.
You may be familiar with using typeof
and instanceof
in JavaScript to test for these conditions. TypeScript understands these conditions and will change type inference accordingly when used in an if
block.
You can use the following conditions to learn the type of a variable:
Type | Predicate |
---|---|
string |
typeof s === "string" |
number |
typeof n === "number" |
boolean |
typeof b === "boolean" |
undefined |
typeof undefined === "undefined" |
function |
typeof f === "function" |
array |
Array.isArray(a) |