找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2546

积分

0

好友

334

主题
发表于 昨天 19:48 | 查看: 1| 回复: 0

先看这段代码,很多人以为这两个类型是一样的:

let value1: any = "hello";
value1.toFixed(2); // 编译通过,但运行时报错

let value2: unknown = "hello";
value2.toFixed(2); // ❌ 编译错误:Object is of type ‘unknown‘

这就是关键区别:any 让你完全绕过 TypeScript 的类型检查,而 unknown 强制你在使用前进行类型检查。

今天我要告诉你:unknown 是 TypeScript 中最被低估的类型

any 的问题:类型安全的“逃生舱口”

any 类型是 TypeScript 的“逃生舱口”。当你使用 any 时,你在告诉 TypeScript:“别检查这个,我知道我在做什么。”

但问题就在这里:你经常不知道你在做什么

真实场景:从 API 接收数据

// ❌ 使用 any - 危险
async function fetchUserData(): Promise<any> {
  const response = await fetch(‘/api/user’);
  return response.json();
}

async function processUser() {
  const user = await fetchUserData();

  // TypeScript 完全信任你
  console.log(user.name.toUpperCase()); // 如果 name 不存在?运行时错误
  console.log(user.age + 5);           // 如果 age 是字符串?奇怪的bug
  user.email.sendNewsletter();         // 如果 email 不是对象?运行时错误
}

使用 any 时,TypeScript 变成了 “AnyScript”。你失去了:

  1. 类型安全:编译器不会检查任何操作
  2. 智能提示:编辑器无法提供属性补全
  3. 重构能力:无法通过类型检查发现影响范围

unknown 的解决方案:安全的未知

unknown 的设计哲学是:“我不知道这是什么类型,所以在使用前你必须告诉我。”

// ✅ 使用 unknown - 安全
async function fetchUserData(): Promise<unknown> {
  const response = await fetch(‘/api/user’);
  return response.json();
}

async function processUser() {
  const user = await fetchUserData();

  // ❌ 这些都会编译错误
  // console.log(user.name);
  // console.log(user.age + 5);
  // user.email.sendNewsletter();

  // ✅ 必须先进行类型检查
  if (isValidUser(user)) {
    // 现在 TypeScript 知道 user 的类型
    console.log(user.name.toUpperCase()); // 安全
    console.log(user.age + 5);           // 安全
  }
}

这就是 unknown 的核心价值:强制你进行类型检查。

实战对比:4个常见场景

场景1:解析 JSON

// ❌ 使用 any
function parseJson(json: string): any {
  return JSON.parse(json);
}

const data = parseJson(‘{“name”: “张三”}’);
console.log(data.name); // 可以访问,但不安全
console.log(data.age);  // 编译通过,但可能是 undefined

// ✅ 使用 unknown
function parseJsonSafe<T>(json: string): unknown {
  return JSON.parse(json);
}

const data = parseJsonSafe(‘{“name”: “张三”}’);

// 必须先验证类型
if (typeof data === ‘object’ && data !== null && ‘name’ in data) {
  const name = (data as any).name; // 需要类型断言
  console.log(name);
}

场景2:处理用户输入

// ❌ 使用 any - 可能隐藏错误
function processInput(input: any) {
  // 假设我们期望 input 是数字
  const result = input * 2;
  return result;
}

processInput(10);     // 20
processInput(“10”);   // “1010”(字符串拼接,不是乘法!)
processInput(null);   // 0(null 被转换为 0)
processInput(undefined); // NaN

// ✅ 使用 unknown - 强制检查
function processInputSafe(input: unknown) {
  if (typeof input === ‘number’) {
    return input * 2;
  }
  if (typeof input === ‘string’ && !isNaN(Number(input))) {
    return Number(input) * 2;
  }
  throw new Error(‘输入必须是数字或数字字符串’);
}

processInputSafe(10);     // 20
processInputSafe(“10”);   // 20
processInputSafe(null);   // ❌ 抛出错误
processInputSafe(“abc”);  // ❌ 抛出错误

场景3:第三方库集成

// ❌ 使用 any - 可能引入运行时错误
import thirdPartyLib from ‘some-library’;

const result: any = thirdPartyLib.doSomething();
result.process(); // 假设 process 存在

// ✅ 使用 unknown - 安全集成
import thirdPartyLib from ‘some-library’;

const result: unknown = thirdPartyLib.doSomething();

// 检查返回值类型
if (typeof result === ‘object’ && result !== null && ‘process’ in result) {
  // 进一步验证 process 是函数
  const maybeFunction = (result as any).process;
  if (typeof maybeFunction === ‘function’) {
    maybeFunction();
  }
}

场景4:类型安全的存储

// ❌ 使用 any - 可能存储错误类型
class Storage {
  private data: any;

  set(key: string, value: any) {
    this.data[key] = value;
  }

  get(key: string): any {
    return this.data[key];
  }
}

const storage = new Storage();
storage.set(‘user’, { name: ‘张三’ });
const user = storage.get(‘user’);
console.log(user.name); // 假设是对象

// 但可能被错误设置
storage.set(‘user’, “只是一个字符串”);
const user2 = storage.get(‘user’);
console.log(user2.name); // ❌ 运行时错误:undefined

// ✅ 使用 unknown - 强制类型检查
class SafeStorage {
  private data: Record<string, unknown> = {};

  set<T>(key: string, value: T) {
    this.data[key] = value;
  }

  get<T>(key: string): T | undefined {
    const value = this.data[key];
    return value as T;
  }

  getWithValidation<T>(
    key: string,
    validator: (val: unknown) => val is T
  ): T | undefined {
    const value = this.data[key];
    return validator(value) ? value : undefined;
  }
}

const safeStorage = new SafeStorage();
safeStorage.set(‘user’, { name: ‘张三’ });

// 必须提供类型或验证器
const user = safeStorage.get<{ name: string }>(‘user’);
if (user) {
  console.log(user.name); // 安全
}

// 或者使用验证器
const isUser = (val: unknown): val is { name: string } => {
  return typeof val === ‘object’ &&
         val !== null &&
         ‘name’ in val &&
         typeof (val as any).name === ‘string’;
};

const validatedUser = safeStorage.getWithValidation(‘user’, isUser);
if (validatedUser) {
  console.log(validatedUser.name); // 安全
}

unknown 的高级技巧

技巧1:联合类型中的 unknown

// unknown 在联合类型中会被吸收
type T1 = unknown | string;  // unknown
type T2 = unknown | number;  // unknown
type T3 = unknown | boolean; // unknown

// 这意味着:unknown 联合任何类型还是 unknown

// 但在交叉类型中不同
type T4 = unknown & string;  // string
type T5 = unknown & number;  // number
type T6 = unknown & boolean; // boolean

技巧2:映射 unknown

// 创建一个安全的解析函数
type SafeParser<T> = (input: unknown) => T | Error;

// 实现一个安全的数字解析器
const safeNumberParser: SafeParser<number> = (input) => {
  if (typeof input === ‘number’) {
    return input;
  }
  if (typeof input === ‘string’ && !isNaN(Number(input))) {
    return Number(input);
  }
  return new Error(`无法解析为数字: ${input}`);
};

// 使用
const result1 = safeNumberParser(42);     // 42
const result2 = safeNumberParser(“42”);   // 42
const result3 = safeNumberParser(“abc”);  // Error

技巧3:递归的 unknown 处理

// 处理深度嵌套的 unknown 数据
function deepProcess(data: unknown): unknown {
  if (Array.isArray(data)) {
    return data.map(item => deepProcess(item));
  }

  if (typeof data === ‘object’ && data !== null) {
    const result: Record<string, unknown> = {};
    for (const [key, value] of Object.entries(data)) {
      result[key] = deepProcess(value);
    }
    return result;
  }

  if (typeof data === ‘string’) {
    return data.trim();
  }

  if (typeof data === ‘number’) {
    return Math.round(data * 100) / 100; // 保留两位小数
  }

  return data;
}

// 处理任意 JSON 数据
const processed = deepProcess({
  name: “  张三  “,
  age: 25.567,
  scores: [90.123, 85.789, null],
  metadata: { created: “2024-01-01 “, active: true }
});

如何迁移:把 any 替换成 unknown

如果你有大量使用 any 的代码,可以这样迁移:

步骤1:全局搜索替换

// 替换前
function process(data: any) {
  // ...
}

// 替换后
function process(data: unknown) {
  // 现在 TypeScript 会提示你需要添加类型检查
}

步骤2:创建类型守卫函数

// 常用的类型守卫
export function isString(value: unknown): value is string {
  return typeof value === ‘string’;
}

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

export function isObject(value: unknown): value is Record<string, unknown> {
  return typeof value === ‘object’ && value !== null;
}

export function isArray(value: unknown): value is unknown[] {
  return Array.isArray(value);
}

// 使用
function process(value: unknown) {
  if (isString(value)) {
    // 这里 value 是 string 类型
    console.log(value.toUpperCase());
  }
}

步骤3:逐步重构

不要一次性重写所有代码,按优先级进行:

  1. 公共 API:先从暴露给外部的函数开始
  2. 核心业务逻辑:处理关键数据的函数
  3. 工具函数:通用的工具函数

什么时候可以用 any

虽然我们推荐使用 unknown,但在某些场景下,any 仍然是必要的:

场景1:迁移遗留 JavaScript 代码

// 逐步迁移时使用
function legacyFunction(data: any): any {
  // 遗留代码,稍后迁移
  return data;
}

// 稍后重构为
function refactoredFunction(data: unknown): unknown {
  // 重构后的代码
  return data;
}

场景2:测试代码

// 测试中简化类型
describe(‘UserService’, () => {
  it(‘should create user’, () => {
    const mockUser: any = {
      name: ‘Test User’,
      email: ‘test@example.com’
    };

    // 在测试中简化类型是合理的
    const result = userService.create(mockUser);
    expect(result).toBeDefined();
  });
});

场景3:真正的动态场景

// 处理真正动态的数据,如代码生成
function evaluateCode(code: string): any {
  // 警告:这是真正的动态执行
  return eval(code);
}

关键原则:每当你使用 any 时,应该有一个明确的理由,并且最好添加注释说明为什么使用 any

unknown vs any:快速对比表

特性 any unknown
类型检查 完全禁用 严格启用
赋值给其他类型 ✅ 可以直接赋值 ❌ 需要类型断言或类型守卫
调用方法 ✅ 可以直接调用 ❌ 需要先确定类型
访问属性 ✅ 可以直接访问 ❌ 需要先确定类型
类型安全 ❌ 不安全 ✅ 安全
智能提示 ❌ 没有提示 ❌ 没有提示(直到类型确定)
推荐程度 ❌ 尽量避免 ✅ 推荐用于未知数据

最后的建议

记住这两个简单的规则:

规则1:向外暴露的 API 用 unknown

// 你的函数接收外部数据时
export function processUserInput(input: unknown) {
  // 必须先验证类型
  if (typeof input === ‘string’) {
    // 处理字符串
  }
}

规则2:内部处理时明确类型

// 函数内部应该使用明确的类型
function calculateTotal(prices: number[]): number {
  // 这里不需要 unknown,我们知道 prices 是数字数组
  return prices.reduce((sum, price) => sum + price, 0);
}

从今天开始,把 any 看作一个“红色警报”。每次你输入 : any 时,问问自己:

  1. 这是真的无法确定类型吗? 还是我只是懒?
  2. 这里用 unknown 会不会更安全? 大多数情况下答案是“会”。

unknown 不是为了让你的生活更困难,而是为了保护你的代码。它强制你思考数据的不确定性,从而编写出更健壮的代码。在 云栈社区 等开发者平台,深入理解并应用这些类型安全知识,能帮助你构建更可靠的应用程序。

这就是 unknownany 强的地方:它让你成为更好的 TypeScript 开发者。




上一篇:MACD-V指标解析:超越传统MACD的量化交易新工具
下一篇:短信接口防刷实战:Java后端如何用Redis限流与业务校验守住防线
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-27 02:55 , Processed in 0.340605 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表