阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > unknown类型与any类型比较

unknown类型与any类型比较

作者:陈川 阅读数:56197人阅读 分类: TypeScript

unknown类型与any类型的基本概念

TypeScript中的unknownany都是顶级类型,但它们的行为方式截然不同。any类型允许绕过类型检查,而unknown类型则强制进行类型检查。这两种类型在处理不确定的数据时各有用途,但它们的类型安全性差异很大。

let anyValue: any = "hello";
let unknownValue: unknown = "world";

// any类型可以直接操作
anyValue.toUpperCase(); // 不会报错

// unknown类型需要类型断言或类型检查
if (typeof unknownValue === "string") {
  unknownValue.toUpperCase(); // 安全操作
}

类型安全性的差异

any类型完全放弃了类型检查,相当于回到了纯JavaScript的开发体验。而unknown类型则要求开发者显式地进行类型检查或断言后才能操作值,这提供了更好的类型安全性。

function processAny(data: any) {
  data.methodThatMightNotExist(); // 编译通过,运行时可能出错
}

function processUnknown(data: unknown) {
  // data.methodThatMightNotExist(); // 编译错误
  if (typeof data === "object" && data !== null && "methodThatMightNotExist" in data) {
    (data as { methodThatMightNotExist: () => void }).methodThatMightNotExist();
  }
}

使用场景对比

any类型通常在以下情况使用:

  • 迁移旧JavaScript代码到TypeScript时的临时解决方案
  • 需要完全绕过类型系统的情况
  • 与第三方库交互时不确定类型结构

unknown类型更适合:

  • 处理来自外部源的不确定数据(如API响应)
  • 实现类型安全的动态代码
  • 编写更严格的泛型函数
// 使用any的API响应处理
function parseResponseAny(response: any) {
  return response.data.items.map((item: any) => item.name);
}

// 使用unknown的API响应处理
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");
}

类型推断行为

当使用any类型时,TypeScript会允许任何操作,不会进行类型推断。而unknown类型会强制进行类型收缩(type narrowing)才能使用值。

let anyVar: any = "test";
let unknownVar: unknown = "test";

// any类型会"传染"给结果
let anyResult = anyVar + 123; // 类型为any

// unknown类型需要明确处理
let unknownResult;
if (typeof unknownVar === "string") {
  unknownResult = unknownVar + 123; // 类型为string
} else {
  unknownResult = "default";
}

与其它类型的交互

unknown类型可以赋值给任何类型(需要类型断言或检查),而any类型也可以赋值给任何类型但不需要检查。反过来,任何类型都可以赋值给unknown,但只有anyunknown可以赋值给any

let a: any;
let u: unknown;
let s: string = "hello";

a = s; // 允许
u = s; // 允许

s = a; // 允许
s = u; // 需要类型断言或检查

s = u as string; // 类型断言
if (typeof u === "string") s = u; // 类型检查

函数返回值中的使用

在函数返回值中,unknownany也有明显区别。返回unknown的函数强制调用者处理类型不确定性,而返回any的函数则把责任完全转移给调用者。

function getAny(): any {
  return JSON.parse(localStorage.getItem("data") || "null");
}

function getUnknown(): unknown {
  return JSON.parse(localStorage.getItem("data") || "null");
}

// 使用any返回值
const anyData = getAny();
anyData.someProperty; // 编译通过,但可能运行时出错

// 使用unknown返回值
const unknownData = getUnknown();
// unknownData.someProperty; // 编译错误
if (typeof unknownData === "object" && unknownData !== null && "someProperty" in unknownData) {
  console.log(unknownData.someProperty); // 安全访问
}

类型断言的不同影响

any类型的断言实际上是不必要的,因为它已经可以当作任何类型使用。而对unknown类型的断言则是将其转换为特定类型的常见方式。

const anyValue: any = "hello";
const unknownValue: unknown = "world";

// any类型断言多余但合法
const anyAsNumber: number = anyValue as number;

// unknown类型断言是必要的
const unknownAsString: string = unknownValue as string;

// 更安全的unknown类型断言方式
function isString(value: unknown): value is string {
  return typeof value === "string";
}

if (isString(unknownValue)) {
  console.log(unknownValue.toUpperCase());
}

在泛型编程中的应用

在泛型编程中,unknown通常比any更安全,因为它不会破坏类型系统的完整性。

// 使用any的泛型缓存 - 不安全
class CacheAny {
  private data: any;
  
  set<T>(value: T): void {
    this.data = value;
  }
  
  get<T>(): T {
    return this.data;
  }
}

// 使用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>(); // 编译通过但类型错误

const cacheUnknown = new CacheUnknown();
cacheUnknown.set<string>("test");
// const num2 = cacheUnknown.get<number>(); // 需要处理undefined情况
const str = cacheUnknown.get<string>(); // 更安全

性能考量

从性能角度看,unknownany在运行时没有区别,因为TypeScript类型只在编译时存在。但在开发体验和代码维护方面,unknown通常能带来更好的长期效益。

// 使用any的快速原型开发
function quickAndDirty(input: any) {
  // 快速编写逻辑
  return input * 2;
}

// 使用unknown的渐进类型完善
function typeSafe(input: unknown) {
  if (typeof input === "number") {
    return input * 2;
  }
  throw new Error("Invalid input type");
}

与第三方库的互操作

当与没有类型定义的第三方JavaScript库交互时,unknown通常比any更安全,因为它强制开发者明确处理不确定的类型。

// 使用any的第三方库调用
declare function legacyLibAny(): any;
const resultAny = legacyLibAny();
resultAny.doSomething(); // 危险,没有类型检查

// 使用unknown的第三方库调用
declare function legacyLibUnknown(): unknown;
const resultUnknown = legacyLibUnknown();
if (
  typeof resultUnknown === "object" &&
  resultUnknown !== null &&
  "doSomething" in resultUnknown &&
  typeof resultUnknown.doSomething === "function"
) {
  resultUnknown.doSomething(); // 安全调用
}

类型守卫的应用

unknown类型与类型守卫(type guards)配合使用效果最佳,可以逐步缩小类型范围,而any则不需要类型守卫。

// 处理复杂未知数据结构的类型守卫
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");
  }
}

在错误处理中的使用

在处理可能抛出异常的代码时,unknown是更好的选择,因为catch子句的error参数类型在TypeScript 4.0+中默认为unknown

// 旧版TypeScript的error是any类型
try {
  // 可能抛出异常的代码
} catch (error: any) {
  console.log(error.message); // 不安全,error可能没有message属性
}

// TypeScript 4.0+的error是unknown类型
try {
  // 可能抛出异常的代码
} catch (error) {
  if (error instanceof Error) {
    console.log(error.message); // 安全
  } else {
    console.log("Unknown error occurred");
  }
}

联合类型中的表现

在联合类型中,unknown吸收所有类型(因为任何类型都可以赋值给unknown),而any则与任何类型联合都会产生any

type T1 = unknown | string; // unknown
type T2 = any | string; // any

function example(value: unknown | string) {
  // value仍然是unknown,需要类型检查
  if (typeof value === "string") {
    console.log(value.length);
  }
}

function example2(value: any | string) {
  // value是any,可以直接操作
  console.log(value.length); // 不安全
}

交叉类型中的表现

在交叉类型中,unknown是中性元素(与任何类型交叉都得到原类型),而any与任何类型交叉都会产生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

在条件类型中的行为

在条件类型中,unknown表现得像一个严格的顶级类型,而any则会破坏条件类型的分布特性。

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

与never类型的关系

unknownnever处于类型系统的两端:unknown是所有类型的超类型,never是所有类型的子类型。而any则同时是超类型和子类型。

declare let neverValue: never;
declare let unknownValue: unknown;
declare let anyValue: any;

neverValue = unknownValue; // 错误
unknownValue = neverValue; // 允许

neverValue = anyValue; // 允许
anyValue = neverValue; // 允许

在映射类型中的表现

当使用映射类型处理unknownany时,它们的行为也有显著差异。

type MakeOptional<T> = {
  [K in keyof T]?: T[K];
};

type OptionalAny = MakeOptional<any>; // { [key: string]: any; }
type OptionalUnknown = MakeOptional<unknown>; // {}

// 处理unknown需要先检查其是否为对象类型
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; }

在函数参数中的协变和逆变

在函数参数位置,unknown表现为逆变(可以接受更具体的类型),而any则破坏了正常的变体规则。

type Handler<T> = (arg: T) => void;

let unknownHandler: Handler<unknown>;
let anyHandler: Handler<any>;

// 可以赋值更具体的handler
unknownHandler = (arg: string) => console.log(arg.length); // 允许
anyHandler = (arg: string) => console.log(arg.length); // 允许

// 但是反过来
let specificHandler: Handler<string>;
specificHandler = unknownHandler; // 错误
specificHandler = anyHandler; // 允许但不安全

在索引签名中的使用

当处理动态属性访问时,unknownany提供了更好的安全性。

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)}`);
    }
  }
}

// 对比any的版本
interface DynamicObjectAny {
  [key: string]: any;
}

function processDynamicObjectAny(obj: DynamicObjectAny) {
  for (const key in obj) {
    const value = obj[key];
    console.log(value.toUpperCase()); // 可能运行时出错
  }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌