在Vue 3 + TypeScript中使用类型守卫(Type Guard)可以帮助你在组件中安全地处理复杂类型,避免类型断言(as
)带来的潜在风险。类型守卫的核心是通过一个函数判断变量类型,让TypeScript在编译时就能识别变量的具体类型。
1. 基本使用场景:验证接口数据
最常见的场景是处理后端返回的不确定类型数据,确保它符合组件所需的接口类型。
步骤1:定义接口和类型守卫函数
<!-- 子组件 Child.vue -->
<template>
<div v-if="isValidUser">
<p>姓名: {{ user.name }}</p>
<p>年龄: {{ user.age }}</p>
</div>
<div v-else>数据格式错误</div>
</template>
<script setup lang="ts">
import { defineProps, computed } from 'vue';
// 1. 定义需要验证的接口
interface User {
name: string;
age: number;
email?: string; // 可选属性
}
// 2. 定义类型守卫函数(核心)
// 函数返回值为 `data is User`,表示"如果返回true,则data是User类型"
function isUser(data: unknown): data is User {
// 逐步验证:先判断是对象且不为null
if (data === null || typeof data !== 'object') {
return false;
}
// 验证必填属性的类型
const user = data as User;
return (
typeof user.name === 'string' && // name必须是字符串
typeof user.age === 'number' && // age必须是数字
(user.email === undefined || typeof user.email === 'string') // 可选属性验证
);
}
// 3. 接收父组件传递的可能不安全的数据
const props = defineProps<{
rawData: unknown; // 明确标记为未知类型,需要验证
}>();
// 4. 使用类型守卫验证数据
const isValidUser = computed(() => isUser(props.rawData));
const user = computed(() => isValidUser.value ? props.rawData : null);
</script>
步骤2:父组件传递数据
<!-- 父组件 Parent.vue -->
<template>
<Child :raw-data="backendData" />
</template>
<script setup lang="ts">
import Child from './Child.vue';
// 模拟后端返回的原始数据(可能格式错误)
const backendData = {
name: "张三",
age: "25" // 这里故意传字符串,模拟错误
};
</script>
此时子组件会通过isUser
函数检测到age
是字符串而非数字,显示"数据格式错误",避免了直接断言导致的运行时错误。
2. 在事件处理中使用类型守卫
处理组件事件时,也可以用类型守卫区分不同的事件参数类型:
<template>
<button @click="handleEvent">触发事件</button>
</template>
<script setup lang="ts">
import { defineEmits } from 'vue';
// 定义两种可能的事件参数类型
type EventData =
| { type: 'success'; message: string }
| { type: 'error'; code: number };
// 定义事件
const emit = defineEmits<{
(e: 'action', data: EventData): void;
}>();
// 触发事件
const handleEvent = () => {
emit('action', { type: 'success', message: '操作成功' });
};
// 类型守卫:判断是否为success类型
function isSuccessEvent(data: EventData): data is { type: 'success'; message: string } {
return data.type === 'success';
}
// 使用类型守卫处理事件
const handleAction = (data: EventData) => {
if (isSuccessEvent(data)) {
// TypeScript自动推断data为success类型,可直接访问message
console.log('成功消息:', data.message);
} else {
// 否则自动推断为error类型,可访问code
console.log('错误代码:', data.code);
}
};
</script>
3. 与ref
/reactive
结合使用
在响应式数据中使用类型守卫,可以安全地访问复杂类型的属性:
<script setup lang="ts">
import { ref } from 'vue';
interface Product {
id: number;
name: string;
}
// 类型守卫
function isProduct(data: unknown): data is Product {
return (
typeof data === 'object' &&
data !== null &&
typeof (data as Product).id === 'number' &&
typeof (data as Product).name === 'string'
);
}
// 响应式数据(初始为unknown)
const product = ref<unknown>(null);
// 模拟从API获取数据
const fetchData = async () => {
const res = await fetch('/api/product');
const data = await res.json();
// 验证通过后再赋值
if (isProduct(data)) {
product.value = data; // 此时TypeScript会识别product为Product类型
} else {
console.error('产品数据格式错误');
}
};
// 使用时的安全访问
const logProduct = () => {
if (isProduct(product.value)) {
console.log('产品名称:', product.value.name); // 安全访问
}
};
</script>
核心要点总结
- 类型守卫函数:返回值格式为
参数 is 类型
,告诉TypeScript如何判断类型。 - 渐进式验证:对复杂类型(如对象),要逐一验证必填属性的类型,避免遗漏。
- 与Vue特性结合:在
props
接收、响应式数据处理、事件传递等场景中,先用类型守卫验证,再安全使用。 - 替代类型断言:相比
as
,类型守卫通过实际检查确保类型正确,减少运行时错误。
简单说,类型守卫就像给数据"验明正身",只有通过检查的才能被认定为目标类型,让TypeScript和你的代码更"放心"。