TypeScript 中 `any` 和 `unknown` 的用法与区别

154 阅读5分钟

TypeScript 中 anyunknown 的用法与区别

目录

  1. 概述
  2. any 类型
  3. unknown 类型
  4. any vs unknown
  5. 最佳实践
  6. 实际应用场景
  7. 项目中的实际应用

概述

TypeScript 提供了两种特殊的顶级类型:anyunknown。虽然它们都可以接收任何类型的值,但它们在类型安全性和使用限制上有很大的区别。

1. any 类型

定义

any 类型是 TypeScript 中最灵活的类型,它可以:

  • 接收任何类型的值
  • 允许访问任何属性
  • 调用任何方法
  • 赋值给任何其他类型 在 TypeScript 中,anyunknown 都是用来表示不确定类型的关键字,它们的用法和语义有显著的区别。理解这两个类型的区别对编写类型安全的代码非常重要。

any 类型表示任何类型的值,使用 any 类型的变量可以被赋予任何值,并且对这些值进行任何操作,不会引发类型错误。这意味着,使用 any 时,TypeScript 会对该值放宽类型检查,因此它丧失了类型安全。

特点:

  • any 类型的值可以赋给任何其他类型的变量。
  • 可以对 any 类型的值进行任何操作(调用方法、访问属性等),TypeScript 不会报错。
  • 使用 any 会丧失类型检查,可能导致运行时错误。

示例

let anyValue: any = 42;
anyValue = "hello";  // 可以
anyValue = true;     // 可以
anyValue.foo();      // 可以(但可能运行时错误)
anyValue.bar.baz;    // 可以(但可能运行时错误)

// 可以赋值给任何类型
let num: number = anyValue;  // 可以
let str: string = anyValue;  // 可以
let value: any;

value = 42;       // 赋值为数字
value = "Hello";  // 赋值为字符串
value = true;     // 赋值为布尔值

// 对 `any` 类型的变量执行任何操作
value.someMethod();  // 不会报错,尽管 `value` 的类型是不确定的

缺点

  • 完全绕过类型检查
  • 可能导致运行时错误
  • 失去 IDE 的智能提示
  • 传播 any 类型,也就是说当一个any类型的变量赋值给一个有数据类型,比如string的变量时有数据类型的变量也将变成any

2. unknown 类型

定义

unknown 是类型安全的 any

  • 可以接收任何类型的值
  • 不能直接访问属性或方法
  • 需要类型检查或类型断言才能使用

unknown 类型表示一个值是未知的类型,但与 any 类型不同,unknown 对应的值不能直接进行操作,必须首先进行类型检查或者类型断言后,才能执行进一步的操作。

特点: unknown 类型的值不能直接赋给其他类型的变量,除非先进行类型检查或类型断言。 对 unknown 类型的值进行操作时,TypeScript 会提示类型错误,必须先进行类型检查。

示例

let unknownValue: unknown = 42;
unknownValue = "hello";  // 可以
unknownValue = true;     // 可以

// 直接使用会报错
unknownValue.foo();        // 错误
unknownValue.length;       // 错误
unknownValue.toString();   // 错误

// 需要类型检查后才能使用
if (typeof unknownValue === "string") {
    console.log(unknownValue.length);  // 现在可以了
}
/ 类型断言
(value as string).toUpperCase(); // 强制断言为字符串后可以调用方法
  1. any 与 unknown 的区别
特性anyunknown
类型检查放宽类型检查严格类型检查
赋值兼容性可以赋值给任何类型不能直接赋值给其他类型,必须进行类型检查
操作兼容性可以直接操作,不会有类型错误必须先进行类型检查或类型断言才能操作
类型安全性丧失类型安全保持类型安全

总结: any 是最宽松的类型,允许你进行任何操作,但会丧失类型安全,容易导致运行时错误。 unknown 更加安全,要求进行类型检查或类型断言,以确保类型安全。

5. 实际应用场景

使用 any 的场景: 动态类型的数据,比如从第三方库或API中获取的数据类型不确定。 暂时不确定类型的情况,可以使用 any 来绕过类型检查。 使用 unknown 的场景: 对外部输入进行严格检查时使用 unknown,避免直接操作不确定类型的数据。 在安全地操作外部输入数据时,强烈推荐使用 unknown。

项目中的实际应用

1. 自定义 Hook 中的类型安全

不安全的写法(使用 any):

const useCheckboxState = (
  options: any[],
  initialValues?: any,
  onChange?: (options: any) => void
) => {
  // 这样写会失去类型检查
  const [selectedValues, setSelectedValues] = useState(initialValues);
  // ...
};

安全的写法(使用具体类型):

interface Option {
  label: string;
  value: string;
}

const useCheckboxState = (
  options: Option[],
  initialValues?: string[],
  onChange?: (options: Option[]) => void
) => {
  const [selectedValues, setSelectedValues] = useState<string[]>(
    initialValues || options.map(opt => opt.value)
  );
  // ...
};

2. 事件处理中的类型安全

不安全的写法:

const handleChange = (event: any) => {
  console.log(event.target.checked);  // 可能运行时错误
};

安全的写法:

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  console.log(event.target.checked);  // 类型安全
};

3. API 响应处理

不安全的写法:

const fetchData = async () => {
  const response = await fetch('/api/data');
  const data: any = await response.json();
  return data;  // 返回 any 类型
};

安全的写法:

interface ApiResponse {
  id: string;
  values: string[];
  options: Option[];
}

const fetchData = async (): Promise<ApiResponse> => {
  const response = await fetch('/api/data');
  const data: unknown = await response.json();
  
  if (isApiResponse(data)) {
    return data;
  }
  throw new Error('Invalid API response');
};

function isApiResponse(data: unknown): data is ApiResponse {
  return (
    typeof data === 'object' &&
    data !== null &&
    'id' in data &&
    'values' in data &&
    'options' in data &&
    Array.isArray((data as ApiResponse).values) &&
    Array.isArray((data as ApiResponse).options)
  );
}

最佳实践总结

  1. 默认使用严格类型,避免使用 any
  2. 当需要接收任何类型值时,优先使用 unknown
  3. 使用类型守卫进行类型缩小
  4. 为 API 响应定义接口
  5. 利用泛型增加代码复用性
  6. 使用类型断言时要确保类型安全

示例:类型守卫的使用

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function isNumber(value: unknown): value is number {
  return typeof value === 'number' && !isNaN(value);
}

function processValue(value: unknown) {
  if (isString(value)) {
    console.log(value.toUpperCase());  // 安全
  } else if (isNumber(value)) {
    console.log(value.toFixed(2));     // 安全
  }
}

结论

  • any 类型应该作为最后的选择,仅在特殊情况下使用
  • unknown 类型提供了更好的类型安全性
  • 合理使用类型系统可以提前发现错误
  • 类型定义虽然会增加一些代码量,但能显著提高代码质量和可维护性