javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > TypeScript  any  unknown

TypeScript 中any 与 unknown的使用与区别

作者:小赵同学WoW

本文用通俗对比和实例告诉你,any会跳过类型检查,而unknown强制你先确认类型再使用,了解如何通过类型收窄安全处理接口数据、异常对象等不确定类型,避免类型不安全扩散到业务代码

摘要

any 会跳过类型检查,unknown 则要求使用前先确认类型。无法确定数据类型时,应优先使用更安全的 unknown。

背景

TypeScript 允许使用 any 和 unknown 接收暂时无法确定类型的值。两者都能容纳不同类型的数据,但使用方式和安全性完全不同:

理解二者的差异,可以避免类型不安全从接口边界扩散到业务代码中。

核 心概念

any

let flexibleValue: any = 'TypeScript';

flexibleValue = 100;
flexibleValue = { name: 'Summer' };

flexibleValue.notExists.deepProperty();

最后一行能够通过类型检查,但运行时会因为 notExistsundefined 而报错。

unknown

let uncertainValue: unknown = 'TypeScript';

uncertainValue = 100;
uncertainValue = { name: 'Summer' };

uncertainValue.toUpperCase(); // 类型报错: 'uncertainValue' is of type 'unknown'.

主要区别

对比项anyunknown
能否接收任意类型可以可以
能否直接访问属性可以不可以
能否直接调用可以不可以
能否赋给具体类型可以不可以,需要先收窄或断言
是否保留类型检查
推荐使用场景临时兼容、渐进迁移外部输入、未知数据

可以把两者理解为:

赋值规则

其他类型赋给any和unknown

几乎所有类型的值都可以赋给 anyunknown

let anyValue: any;
let unknownValue: unknown;

anyValue = 'text';
anyValue = 100;
anyValue = null;

unknownValue = 'text';
unknownValue = 100;
unknownValue = null;

any赋给其他类型

any 可以直接赋给具体类型。这个过程不会验证运行时数据是否真的符合目标类型。

const source: any = { name: 'Summer' };
const count: number = source;

console.log(count.toFixed(2)); // 运行时报错

count 在编译阶段被视为 number,但它在运行时仍然是一个对象。

unknown赋给其他类型

unknown 不能直接赋给具体类型,只能赋给 unknownany。要赋给其他类型,必须先进行类型收窄或显式断言。

const sourceValue: unknown = 'TypeScript';

const text: string = sourceValue; // 类型报错: Type 'unknown' is not assignable to type 'string'.

if (typeof sourceValue === 'string') {
  const safeText: string = sourceValue;

  console.log(safeText.toUpperCase()); // 输出: TYPESCRIPT
}

操作权限

any允许任意操作

编译器会放行对 any 的属性访问、方法调用和函数调用。

declare const dynamicValue: any;

dynamicValue.name;
dynamicValue.getName();
dynamicValue();
new dynamicValue();

这些操作是否有效,只能在运行时确定。

unknown只允许安全操作

未收窄的 unknown 不能直接访问属性、调用方法或参与要求具体类型的运算。可以进行相等性判断,或者通过类型检查缩小范围。

declare const pendingValue: unknown;

if (pendingValue === null) {
  console.log('值为空'); // 输出: 值为空
}

if (typeof pendingValue === 'number') {
  console.log(pendingValue + 1);
}

unknown的类型收窄

使用typeof

typeof 适合收窄 stringnumberbooleanbigintsymbolundefinedfunction 等类型。

/**
 * 将未知值转换为可展示文本。
 *
 * @param {unknown} value - 需要处理的未知值。
 * @returns {string} 转换后的文本。
 */
function formatUnknownValue(value: unknown): string {
  if (typeof value === 'string') {
    return value.trim();
  }

  if (typeof value === 'number') {
    return value.toFixed(2);
  }

  return String(value);
}

console.log(formatUnknownValue(' TypeScript ')); // 输出: TypeScript
console.log(formatUnknownValue(12)); // 输出: 12.00

使用Array.isArray()

数组在 JavaScript 中的 typeof 结果是 'object',因此通常使用 Array.isArray() 判断。

/**
 * 获取未知数组的元素数量。
 *
 * @param {unknown} value - 需要判断的未知值。
 * @returns {number} 数组的元素数量;非数组返回 `0`。
 */
function getArrayLength(value: unknown): number {
  if (Array.isArray(value)) {
    return value.length;
  }

  return 0;
}

console.log(getArrayLength([1, 2, 3])); // 输出: 3
console.log(getArrayLength('123')); // 输出: 0

使用instanceof

instanceof 适合判断类实例和内置对象。

/**
 * 获取错误信息。
 *
 * @param {unknown} error - 捕获到的未知错误。
 * @returns {string} 可展示的错误信息。
 */
function getErrorMessage(error: unknown): string {
  if (error instanceof Error) {
    return error.message;
  }

  return String(error);
}

console.log(getErrorMessage(new Error('请求失败'))); // 输出: 请求失败

使用自定义类型守卫

处理对象数据时,仅判断 typeof value === 'object' 通常不够,还需要排除 null 并检查必要属性。

interface User {
  id: number;
  name: string;
}

/**
 * 判断未知值是否符合 `User` 结构。
 *
 * @param {unknown} value - 需要验证的未知值。
 * @returns {boolean} 值符合 `User` 结构时返回 `true`。
 */
function isUser(value: unknown): value is User {
  if (typeof value !== 'object' || value === null) {
    return false;
  }

  const candidate = value as Record<string, unknown>;

  return typeof candidate.id === 'number' && typeof candidate.name === 'string';
}

const receivedUser: unknown = { id: 1, name: 'Summer' };

if (isUser(receivedUser)) {
  console.log(receivedUser.name.toUpperCase()); // 输出: SUMMER
}

这里的断言只把对象转换为键值未知的 Record<string, unknown>,随后仍逐项检查属性类型,没有直接断言为 User

实际开发场景

处理接口或存储中的未知数据

外部数据可能不符合预期类型,应先以 unknown 接收,再进行运行时验证。

const parsedData: unknown = JSON.parse('{"id":1,"name":"Summer"}');

if (isUser(parsedData)) {
  console.log(parsedData.name); // 输出: Summer
}

JSON.parse() 自身的返回类型是 any。通过显式标注 unknown,可以阻止未经验证的数据直接进入业务逻辑。

处理catch中的错误

JavaScript 允许抛出任意值,因此捕获到的内容不一定是 Error 实例。在启用 useUnknownInCatchVariables 时,catch 变量会按 unknown 处理。

try {
  throw new Error('网络异常');
} catch (error: unknown) {
  console.log(getErrorMessage(error)); // 输出: 网络异常
}

迁移旧项目

当旧 JavaScript 项目一次性补齐所有类型的成本过高时,可以短期使用 any 维持编译,再逐步替换为真实类型或 unknown

使用 any 时应尽量:

any的传播问题

any 会污染后续类型推导。一旦数据被标记为 any,它可以无检查地流入其他变量和函数。

interface Product {
  name: string;
  price: number;
}

const rawProduct: any = { name: '键盘', price: '299' };
const product: Product = rawProduct;

console.log(product.price.toFixed(2)); // 运行时报错

如果把 rawProduct 标注为 unknown,赋值给 Product 时会立即产生类型错误,迫使开发者先验证数据结构。

与泛型的区别

当输入和输出类型之间存在关联时,应使用泛型保留类型信息,而不是使用 any

/**
 * 原样返回传入的值,并保留其具体类型。
 *
 * @template T
 * @param {T} value - 需要返回的值。
 * @returns {T} 与入参相同类型的值。
 */
function identity<T>(value: T): T {
  return value;
}

const inferredText = identity('TypeScript');
const inferredNumber = identity(100);

console.log(inferredText.toUpperCase()); // 输出: TYPESCRIPT
console.log(inferredNumber.toFixed(2)); // 输出: 100.00

泛型表达“类型暂时由调用方决定”,unknown 表达“类型目前未知且必须验证”,any 则表达“跳过类型检查”。

联合类型与交叉类型中的表现

unknown

type UnknownUnion = unknown | string;
type UnknownIntersection = unknown & string;
type UnknownKeys = keyof unknown;

any

any 通常会吞掉与它组合的类型信息,使结果继续表现为 any。这也是 any 容易扩散的原因之一。

type AnyUnion = any | string;
type AnyIntersection = any & string;
type AnyKeys = keyof any;

其中 AnyUnion 和 AnyIntersection 都是 any,AnyKeys 是 string | number | symbol。

类型断言的注意事项

类型断言不会验证运行时数据,只是告诉编译器按指定类型处理。

const externalValue: unknown = { name: 'Summer' };
const assertedUser = externalValue as User;

console.log(assertedUser.id.toFixed(2)); // 运行时报错

as unknown as TargetType 形式的双重断言能够绕过更多类型限制,但不会让数据真的符合目标结构。除非能够从其他可靠条件证明类型,否则不应使用它代替运行时校验。

适用场景

常见误区

误区 1:unknown和any只是名称不同

误区 2:使用any可以解决类型错误

误区 3:类型断言等于数据校验

误区 4:所有不确定的值都应该使用any

实践建议

  1. 默认选择 unknown,将类型验证放在数据进入系统的边界。
  2. 尽量减少显式 any,并限制隐式 any,建议开启 noImplicitAny 或 strict。
  3. 不让 any 通过公共函数参数、返回值或共享状态扩散。
  4. 使用 typeof、instanceof、Array.isArray() 或自定义类型守卫收窄 unknown。
  5. 对复杂外部数据执行完整的运行时结构校验,不要仅依赖类型断言。
  6. 输入和输出类型存在关联时使用泛型,不要用 any 丢失类型信息。

到此这篇关于TypeScript 中any 与 unknown的使用与区别的文章就介绍到这了,更多相关TypeScript any unknown内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文