Y
Published on

TypeScript Comparing 'any' and 'unknown' Types

Authors
  • avatar
    Name
    Yinhuan Yuan
    Twitter

Introduction

TypeScript, a statically typed superset of JavaScript, has gained immense popularity due to its ability to catch errors at compile-time and provide better tooling support. Two of its type annotations, any and unknown, are often misunderstood or used interchangeably. In this blog post, we'll dive deep into these types, explore their differences, and discuss when to use each.

The 'any' Type

The any type is TypeScript's escape hatch from its type system. It allows you to opt-out of type checking for a particular value.

Characteristics of 'any':
  1. No Type Checking: Variables of type any can hold values of any type.
  2. Allows All Operations: You can perform any operation on an any type without type checks.
  3. Propagates through Operations: Using an any value in an operation results in an any type.
Example of 'any':
let anyValue: any = 10
anyValue = 'Hello' // OK
anyValue = true // OK

let num: number = anyValue // OK (but potentially unsafe)
console.log(anyValue.nonExistentMethod()) // No compile-time error

The 'unknown' Type

Introduced in TypeScript 3.0, the unknown type is a type-safe counterpart of any.

Characteristics of 'unknown':
  1. Type-safe: Variables of type unknown can hold any value, but TypeScript won't allow operations on an unknown without type checks.
  2. Requires Type Checking: You must narrow the type (through type guards or assertions) before performing operations.
  3. Doesn't Propagate: Using an unknown value in an operation doesn't implicitly spread the unknown type.
Example of 'unknown':
let unknownValue: unknown = 10
unknownValue = 'Hello' // OK
unknownValue = true // OK

let num: number = unknownValue // Error: Type 'unknown' is not assignable to type 'number'

if (typeof unknownValue === 'number') {
  let num: number = unknownValue // OK
}

console.log(unknownValue.toString()) // Error: Object is of type 'unknown'

Comparing 'any' and 'unknown'

  1. Type Safety:

    • any: Bypasses all type checks.
    • unknown: Enforces type checks, making it much safer.
  2. Assignability:

    • any: Can be assigned to any other type.
    • unknown: Can only be assigned to unknown or any.
  3. Operations:

    • any: Allows all operations without checks.
    • unknown: Requires type narrowing before operations.
  4. Best Practices:

    • Use any when you need to opt-out of type checking entirely (e.g., when working with dynamic content or during migration from JavaScript).
    • Use unknown when you want to accept any value but ensure type-safe operations later.

When to Use 'unknown'

  1. API Responses: When working with responses from APIs where the structure isn't known at compile-time.
async function fetchData(): Promise<unknown> {
  const response = await fetch('https://api.example.com/data')
  return response.json()
}

const data = await fetchData()
if (typeof data === 'object' && data !== null && 'name' in data) {
  console.log(data.name) // TypeScript knows 'data' has a 'name' property
}
  1. Type Assertions: When you need to work with a value whose type you know but TypeScript doesn't.
function processValue(value: unknown) {
  if (typeof value === 'string') {
    console.log(value.toUpperCase())
  } else if (Array.isArray(value)) {
    console.log(value.length)
  }
}

Conclusion

While any provides flexibility, it comes at the cost of type safety. The unknown type offers a balance between flexibility and safety, encouraging developers to think about types and perform necessary checks.

As a best practice:

  • Avoid any unless absolutely necessary.
  • Use unknown when you need to accept any value but want to maintain type safety.
  • Always narrow unknown types before performing operations on them.

By understanding and correctly using any and unknown, you can write more robust and maintainable TypeScript code, leveraging the full power of TypeScript's type system while still handling dynamic or unknown data when needed.