类型谓词与自定义类型守卫
类型谓词与自定义类型守卫
TypeScript的类型系统允许开发者通过类型谓词和自定义类型守卫来增强类型推断能力。这两种机制在复杂类型场景下尤其有用,能够帮助编译器理解代码中的类型变化。
类型谓词的基本概念
类型谓词是TypeScript中一种特殊的返回值类型注解,格式为parameterName is Type
。它通常用在用户自定义的类型守卫函数中,向编译器表明:如果函数返回true,则参数属于特定类型。
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
function isCat(animal: Cat | Dog): animal is Cat {
return (animal as Cat).meow !== undefined;
}
在这个例子中,isCat
函数不仅返回布尔值,还通过animal is Cat
谓词告诉TypeScript:当函数返回true时,参数animal
一定是Cat
类型。
自定义类型守卫的实现
自定义类型守卫本质上是返回类型谓词的函数。它们可以封装复杂的类型判断逻辑,使代码更清晰且类型安全。
type Primitive = string | number | boolean | symbol | null | undefined;
function isPrimitive(value: unknown): value is Primitive {
return (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean' ||
typeof value === 'symbol' ||
value === null ||
value === undefined
);
}
function processValue(value: unknown) {
if (isPrimitive(value)) {
// 这里value被推断为Primitive类型
console.log(value.toString());
} else {
// 这里value被推断为object类型
console.log(Object.keys(value));
}
}
联合类型与类型守卫
类型守卫在处理联合类型时特别有用,它能帮助TypeScript缩小类型范围。
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; sideLength: number }
| { kind: 'triangle'; base: number; height: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
// 这里shape被推断为{ kind: 'circle'; radius: number }
return Math.PI * shape.radius ** 2;
case 'square':
return shape.sideLength ** 2;
case 'triangle':
return (shape.base * shape.height) / 2;
}
}
复杂类型守卫示例
对于更复杂的场景,可以创建组合多个条件的类型守卫。
interface Admin {
role: 'admin';
permissions: string[];
}
interface User {
role: 'user';
lastLogin: Date;
}
type Person = Admin | User;
function isAdmin(person: Person): person is Admin {
return person.role === 'admin';
}
function hasPermission(person: Person, permission: string): boolean {
if (isAdmin(person)) {
// 这里person被推断为Admin类型
return person.permissions.includes(permission);
}
return false;
}
类型谓词的高级用法
类型谓词可以与泛型结合,创建更灵活的类型守卫。
function isArrayOf<T>(
arr: unknown,
check: (item: unknown) => item is T
): arr is T[] {
return Array.isArray(arr) && arr.every(check);
}
function isString(value: unknown): value is string {
return typeof value === 'string';
}
const data: unknown = ['a', 'b', 'c'];
if (isArrayOf(data, isString)) {
// 这里data被推断为string[]
data.forEach(s => console.log(s.toUpperCase()));
}
运行时类型检查与类型守卫
类型守卫常用于将动态数据(如API响应)转换为静态类型。
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
}
function isApiResponse<T>(
response: unknown,
checkData?: (data: unknown) => data is T
): response is ApiResponse<T> {
if (typeof response !== 'object' || response === null) {
return false;
}
const res = response as Record<string, unknown>;
if (typeof res.success !== 'boolean') {
return false;
}
if (res.success && checkData && 'data' in res) {
return checkData(res.data);
}
return true;
}
// 使用示例
const rawResponse = await fetch('/api/data');
const response = await rawResponse.json();
if (isApiResponse<string[]>(response, isArrayOf(isString))) {
if (response.success) {
console.log(response.data); // string[]
} else {
console.error(response.error);
}
}
类型守卫的性能考虑
虽然类型守卫提供了强大的类型安全,但需要注意其运行时开销。
// 简单但高效的类型守卫
function isNumber(value: unknown): value is number {
return typeof value === 'number';
}
// 复杂但可能低效的类型守卫
function isComplexObject(value: unknown): value is ComplexType {
if (typeof value !== 'object' || value === null) return false;
const obj = value as Record<string, unknown>;
return (
typeof obj.id === 'string' &&
typeof obj.timestamp === 'number' &&
Array.isArray(obj.items) &&
// 更多复杂的检查...
);
}
类型守卫与类型断言的区别
类型守卫和类型断言(as
)有本质区别:类型守卫会执行运行时检查,而类型断言只是告诉编译器"相信我"。
// 不安全的类型断言
const unsafeCast = someValue as string;
// 安全的类型守卫
if (typeof someValue === 'string') {
// 这里someValue被安全地推断为string
const safeValue = someValue;
}
类型谓词的限制与注意事项
类型谓词虽然强大,但也有其限制:
- 类型谓词不能检查不存在的属性
- 过度使用可能导致代码复杂化
- 错误实现的类型守卫可能导致运行时错误
// 错误的类型守卫实现
function isBadGuard(value: unknown): value is string {
// 这个实现实际上不能保证value是string
return true;
}
// 使用错误的类型守卫
const test: unknown = 123;
if (isBadGuard(test)) {
console.log(test.toUpperCase()); // 运行时错误!
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn