TypeScript's `unknown` type

TypeScript's `unknown` type

Any why you should stop declaring all variables as type `any`

In TypeScript, both unknown and any types represent values that could be anything. However, they are used in quite different contexts due to their safety characteristics.

any Type

The any type is essentially TypeScript's way of saying, "turn off type checking for this variable." When you use any, you're telling TypeScript to let you work with a variable without enforcing any type checks. This can be useful when you're dealing with dynamic content that you don't have type information for, but it comes at the cost of losing type safety, making the code prone to runtime errors.

Using any disables TypeScript's type checking, which is a key feature of the language to catch errors at compile time.

anything: any = "hello";
anything = 5; // No error
anything = {}; // No error

unknown Type

The unknown type represents any value, similar to any. However, unknown is much safer because TypeScript enforces that you perform some kind of type checking before you can use the value.

When you use unknown, TypeScript ensures that you can't just assume the type of the value and must explicitly check or assert the type before performing operations on it. This adds a layer of type safety, making sure that you're aware of the variable's type and its capabilities before using it.

let something: unknown = "hello";
// TypeScript will enforce a type check or assertion
if (typeof something === "string") {
    console.log(something.toUpperCase()); // This is safe
}

// Directly calling methods or properties will result in an error
// something.toUpperCase(); // Error: Object is of type 'unknown'.

When to Use unknown

unknown is preferred over any when you want to maintain type safety for variables that come from dynamic or unknown sources. It forces you to perform explicit type checking or casting, reducing the risk of runtime errors. It's particularly useful in the following scenarios:

  1. API responses: When you're not sure of the structure of the response.
async function fetchUserData(url: string): Promise<unknown> {
    const response = await fetch(url);
    return response.json();
}

// You must check the type before using it
const userData = await fetchUserData("https://api.example.com/user");
if (typeof userData === "object" && userData !== null && "username" in userData) {
    console.log(userData.username); // Safe to access `username`
}
  1. Third-party libraries: When interacting with libraries without type definitions, or if the library can return multiple types.
declare function getLibraryData(): unknown;

const data = getLibraryData();
if (Array.isArray(data)) {
    // Now it's safe to treat `data` as an array
    data.forEach(item => console.log(item));
}
  1. Error handling: When catching errors, as TypeScript types error objects as unknown.
try {
    // Some operation that may fail
} catch (error: unknown) {
    if (error instanceof Error) {
        console.log(error.message);
    } else {
        console.log("An unknown error occurred");
    }
}

While any provides the ultimate flexibility by opting out of type checking, unknown offers a balance between flexibility and safety, ensuring that any operations on unknown types are explicitly checked.