先看这段代码,很多人以为这两个类型是一样的:
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”。你失去了:
- 类型安全:编译器不会检查任何操作
- 智能提示:编辑器无法提供属性补全
- 重构能力:无法通过类型检查发现影响范围
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:逐步重构
不要一次性重写所有代码,按优先级进行:
- 公共 API:先从暴露给外部的函数开始
- 核心业务逻辑:处理关键数据的函数
- 工具函数:通用的工具函数
什么时候可以用 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 时,问问自己:
- 这是真的无法确定类型吗? 还是我只是懒?
- 这里用
unknown 会不会更安全? 大多数情况下答案是“会”。
unknown 不是为了让你的生活更困难,而是为了保护你的代码。它强制你思考数据的不确定性,从而编写出更健壮的代码。在 云栈社区 等开发者平台,深入理解并应用这些类型安全知识,能帮助你构建更可靠的应用程序。
这就是 unknown 比 any 强的地方:它让你成为更好的 TypeScript 开发者。