Comparison between the unknown type and the any type
Basic Concepts of unknown and any Types
In TypeScript, both unknown
and any
are top-level types, but they behave very differently. The any
type allows bypassing type checking, while the unknown
type enforces type checking. These two types serve different purposes when handling uncertain data, but they differ significantly in type safety.
let anyValue: any = "hello";
let unknownValue: unknown = "world";
// any type can be directly operated on
anyValue.toUpperCase(); // no error
// unknown type requires type assertion or type checking
if (typeof unknownValue === "string") {
unknownValue.toUpperCase(); // safe operation
}
Differences in Type Safety
The any
type completely bypasses type checking, equivalent to returning to pure JavaScript development. In contrast, the unknown
type requires developers to explicitly perform type checks or assertions before operating on values, providing better type safety.
function processAny(data: any) {
data.methodThatMightNotExist(); // compiles, but may fail at runtime
}
function processUnknown(data: unknown) {
// data.methodThatMightNotExist(); // compile error
if (typeof data === "object" && data !== null && "methodThatMightNotExist" in data) {
(data as { methodThatMightNotExist: () => void }).methodThatMightNotExist();
}
}
Use Case Comparison
The any
type is typically used in the following scenarios:
- Temporary solutions when migrating old JavaScript code to TypeScript
- Cases where the type system needs to be completely bypassed
- Interacting with third-party libraries with uncertain type structures
The unknown
type is more suitable for:
- Handling uncertain data from external sources (e.g., API responses)
- Implementing type-safe dynamic code
- Writing stricter generic functions
// API response handling with any
function parseResponseAny(response: any) {
return response.data.items.map((item: any) => item.name);
}
// API response handling with unknown
function parseResponseUnknown(response: unknown) {
if (
typeof response === "object" &&
response !== null &&
"data" in response &&
typeof response.data === "object" &&
response.data !== null &&
"items" in response.data &&
Array.isArray(response.data.items)
) {
return response.data.items
.filter((item): item is { name: string } => typeof item?.name === "string")
.map((item) => item.name);
}
throw new Error("Invalid response structure");
}
Type Inference Behavior
When using the any
type, TypeScript allows any operation without type inference. In contrast, the unknown
type requires type narrowing to use the value.
let anyVar: any = "test";
let unknownVar: unknown = "test";
// any type "infects" the result
let anyResult = anyVar + 123; // type is any
// unknown type requires explicit handling
let unknownResult;
if (typeof unknownVar === "string") {
unknownResult = unknownVar + 123; // type is string
} else {
unknownResult = "default";
}
Interaction with Other Types
The unknown
type can be assigned to any type (with type assertion or checking), while the any
type can also be assigned to any type without checking. Conversely, any type can be assigned to unknown
, but only any
and unknown
can be assigned to any
.
let a: any;
let u: unknown;
let s: string = "hello";
a = s; // allowed
u = s; // allowed
s = a; // allowed
s = u; // requires type assertion or checking
s = u as string; // type assertion
if (typeof u === "string") s = u; // type checking
Usage in Function Return Values
In function return values, unknown
and any
also differ significantly. A function returning unknown
forces the caller to handle type uncertainty, while a function returning any
shifts the responsibility entirely to the caller.
function getAny(): any {
return JSON.parse(localStorage.getItem("data") || "null");
}
function getUnknown(): unknown {
return JSON.parse(localStorage.getItem("data") || "null");
}
// Using any return value
const anyData = getAny();
anyData.someProperty; // compiles but may fail at runtime
// Using unknown return value
const unknownData = getUnknown();
// unknownData.someProperty; // compile error
if (typeof unknownData === "object" && unknownData !== null && "someProperty" in unknownData) {
console.log(unknownData.someProperty); // safe access
}
Different Impacts of Type Assertions
Type assertions on any
are unnecessary since it can already be used as any type. In contrast, type assertions on unknown
are a common way to convert it to a specific type.
const anyValue: any = "hello";
const unknownValue: unknown = "world";
// any type assertion is redundant but legal
const anyAsNumber: number = anyValue as number;
// unknown type assertion is necessary
const unknownAsString: string = unknownValue as string;
// Safer way to assert unknown type
function isString(value: unknown): value is string {
return typeof value === "string";
}
if (isString(unknownValue)) {
console.log(unknownValue.toUpperCase());
}
Application in Generic Programming
In generic programming, unknown
is generally safer than any
because it doesn't break the integrity of the type system.
// Unsafe generic cache using any
class CacheAny {
private data: any;
set<T>(value: T): void {
this.data = value;
}
get<T>(): T {
return this.data;
}
}
// Safer generic cache using unknown
class CacheUnknown {
private data: unknown;
set<T>(value: T): void {
this.data = value;
}
get<T>(): T | undefined {
if (this.data !== undefined) {
return this.data as T;
}
return undefined;
}
}
const cacheAny = new CacheAny();
cacheAny.set<string>("test");
const num = cacheAny.get<number>(); // compiles but is type-unsafe
const cacheUnknown = new CacheUnknown();
cacheUnknown.set<string>("test");
// const num2 = cacheUnknown.get<number>(); // requires handling undefined case
const str = cacheUnknown.get<string>(); // safer
Performance Considerations
From a performance perspective, unknown
and any
have no runtime differences since TypeScript types exist only at compile time. However, in terms of development experience and code maintainability, unknown
generally offers better long-term benefits.
// Quick prototyping with any
function quickAndDirty(input: any) {
// quickly write logic
return input * 2;
}
// Gradual type refinement with unknown
function typeSafe(input: unknown) {
if (typeof input === "number") {
return input * 2;
}
throw new Error("Invalid input type");
}
Interoperability with Third-Party Libraries
When interacting with third-party JavaScript libraries lacking type definitions, unknown
is generally safer than any
because it forces developers to explicitly handle uncertain types.
// Calling a third-party library with any
declare function legacyLibAny(): any;
const resultAny = legacyLibAny();
resultAny.doSomething(); // dangerous, no type checking
// Calling a third-party library with unknown
declare function legacyLibUnknown(): unknown;
const resultUnknown = legacyLibUnknown();
if (
typeof resultUnknown === "object" &&
resultUnknown !== null &&
"doSomething" in resultUnknown &&
typeof resultUnknown.doSomething === "function"
) {
resultUnknown.doSomething(); // safe call
}
Application of Type Guards
The unknown
type works best with type guards, allowing gradual narrowing of the type scope, while any
doesn't require type guards.
// Type guard for complex unknown data structures
interface User {
id: number;
name: string;
email?: string;
}
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
typeof value.id === "number" &&
"name" in value &&
typeof value.name === "string" &&
(!("email" in value) || typeof value.email === "string")
);
}
function processUser(data: unknown) {
if (isUser(data)) {
console.log(`User: ${data.name}, ID: ${data.id}`);
if (data.email) {
console.log(`Email: ${data.email}`);
}
} else {
console.error("Invalid user data");
}
}
Usage in Error Handling
When handling code that may throw exceptions, unknown
is a better choice because the error parameter in catch clauses is of type unknown
by default in TypeScript 4.0+.
// In older TypeScript versions, error is of type any
try {
// code that may throw
} catch (error: any) {
console.log(error.message); // unsafe, error may not have a message property
}
// In TypeScript 4.0+, error is of type unknown
try {
// code that may throw
} catch (error) {
if (error instanceof Error) {
console.log(error.message); // safe
} else {
console.log("Unknown error occurred");
}
}
Behavior in Union Types
In union types, unknown
absorbs all types (since any type can be assigned to unknown
), while any
combined with any type results in any
.
type T1 = unknown | string; // unknown
type T2 = any | string; // any
function example(value: unknown | string) {
// value remains unknown, requires type checking
if (typeof value === "string") {
console.log(value.length);
}
}
function example2(value: any | string) {
// value is any, can be directly operated on
console.log(value.length); // unsafe
}
Behavior in Intersection Types
In intersection types, unknown
is a neutral element (intersecting with any type yields the original type), while any
intersecting with any type results in any
.
type T3 = unknown & string; // string
type T4 = any & string; // any
interface Person {
name: string;
age: number;
}
type PartialPerson = Partial<Person> & unknown; // Partial<Person>
type PartialPersonAny = Partial<Person> & any; // any
Behavior in Conditional Types
In conditional types, unknown
behaves like a strict top-level type, while any
breaks the distributive property of conditional types.
type IsString<T> = T extends string ? true : false;
type A = IsString<unknown>; // false
type B = IsString<any>; // boolean (true | false)
type FilterStrings<T> = T extends string ? T : never;
type C = FilterStrings<unknown | string | number>; // string
type D = FilterStrings<any | string | number>; // string | any
Relationship with the never Type
unknown
and never
are at opposite ends of the type system: unknown
is the supertype of all types, while never
is the subtype of all types. any
, however, is both a supertype and a subtype.
declare let neverValue: never;
declare let unknownValue: unknown;
declare let anyValue: any;
neverValue = unknownValue; // error
unknownValue = neverValue; // allowed
neverValue = anyValue; // allowed
anyValue = neverValue; // allowed
Behavior in Mapped Types
When processing unknown
and any
with mapped types, their behaviors also differ significantly.
type MakeOptional<T> = {
[K in keyof T]?: T[K];
};
type OptionalAny = MakeOptional<any>; // { [key: string]: any; }
type OptionalUnknown = MakeOptional<unknown>; // {}
// Handling unknown requires checking if it's an object type first
type SafeMakeOptional<T> = unknown extends T
? {}
: T extends object
? {
[K in keyof T]?: T[K];
}
: T;
type Test1 = SafeMakeOptional<any>; // {}
type Test2 = SafeMakeOptional<unknown>; // {}
type Test3 = SafeMakeOptional<{ a: number; b: string }>; // { a?: number; b?: string; }
Covariance and Contravariance in Function Parameters
In function parameter positions, unknown
behaves contravariantly (can accept more specific types), while any
breaks normal variance rules.
type Handler<T> = (arg: T) => void;
let unknownHandler: Handler<unknown>;
let anyHandler: Handler<any>;
// Can assign more specific handlers
unknownHandler = (arg: string) => console.log(arg.length); // allowed
anyHandler = (arg: string) => console.log(arg.length); // allowed
// But not the other way around
let specificHandler: Handler<string>;
specificHandler = unknownHandler; // error
specificHandler = anyHandler; // allowed but unsafe
Usage in Index Signatures
When dealing with dynamic property access, unknown
provides better safety than any
.
interface DynamicObject {
[key: string]: unknown;
}
function processDynamicObject(obj: DynamicObject) {
for (const key in obj) {
const value = obj[key];
if (typeof value === "string") {
console.log(`String value at ${key}: ${value.toUpperCase()}`);
} else if (typeof value === "number") {
console.log(`Number value at ${key}: ${value.toFixed(2)}`);
}
}
}
// Comparison with any version
interface DynamicObjectAny {
[key: string]: any;
}
function processDynamicObjectAny(obj: DynamicObjectAny) {
for (const key in obj) {
const value = obj[key];
console.log(value.toUpperCase()); // may fail at runtime
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:never类型与void类型