unknown类型与any类型比较
unknown类型与any类型的基本概念
TypeScript中的unknown
和any
都是顶级类型,但它们的行为方式截然不同。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
,但只有any
和unknown
可以赋值给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; // 类型检查
函数返回值中的使用
在函数返回值中,unknown
和any
也有明显区别。返回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>(); // 更安全
性能考量
从性能角度看,unknown
和any
在运行时没有区别,因为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类型的关系
unknown
和never
处于类型系统的两端:unknown
是所有类型的超类型,never
是所有类型的子类型。而any
则同时是超类型和子类型。
declare let neverValue: never;
declare let unknownValue: unknown;
declare let anyValue: any;
neverValue = unknownValue; // 错误
unknownValue = neverValue; // 允许
neverValue = anyValue; // 允许
anyValue = neverValue; // 允许
在映射类型中的表现
当使用映射类型处理unknown
和any
时,它们的行为也有显著差异。
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; // 允许但不安全
在索引签名中的使用
当处理动态属性访问时,unknown
比any
提供了更好的安全性。
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
上一篇:never类型与void类型