Vue3 新手必看的 10 个避坑知识点
Vue3 搭配 <script setup> 极大简化了开发,但响应式、API 用法、生命周期、组件通信、样式穿透等细节,新手极易踩坑。下面整理 10 个高频必避坑点,附错误写法、正确写法+总结,直接收藏可用。
1. 分不清 ref 和 reactive 适用场景
坑点
用 reactive存字符串、数字、布尔基本类型,响应式失效直接解构 reactive对象,丢失响应式全部混用,代码维护混乱
正确用法
<script setup>
import { ref, reactive, toRefs } from 'vue'
// 基础类型:string / number / boolean → 用 ref
const count = ref(0)
// 对象、数组 → 推荐用 reactive
const user = reactive({ name: '张三', age: 20 })
// 解构保持响应式,必须搭配 toRefs
const { name, age } = toRefs(user)
</script>避坑口诀
基础类型用 ref,对象数组用 reactive,解构必用 toRefs。
2. script 里使用 ref 忘记加 .value
坑点
const count = ref(0)
count++ // ❌ 无效,视图不更新
console.log(count) // 拿到的是 ref 包装对象
正确写法
const count = ref(0)
count.value++
console.log(count.value)
注意
模板内自动解包不用 .value,script 逻辑里必须加 .value。
3. 直接修改 Props 传值
坑点
<script setup>
const props = defineProps({ num: Number })
props.num++ // ❌ 违规,单向数据流,控制台告警
</script>
正确做法
只读:用 ref本地拷贝一份需要修改:用 defineEmits通知父组件变更
<script setup>
const props = defineProps({ num: Number })
const emit = defineEmits(['update:num'])
// 本地拷贝
const localNum = ref(props.num)
// 通知父组件更新
const changeNum = () => {
emit('update:num', localNum.value + 1)
}
</script>
4. 直接赋值 / 解构导致响应式丢失
坑点
// 1. 直接解构 reactive 丢失响应式
const user = reactive({ name: '李四' })
const { name } = user
// 2. 直接覆盖 reactive 整个对象
const list = reactive([1,2,3])
list = [4,5,6]
正确写法
// 解构用 toRefs
const { name } = toRefs(user)
// 需要整体替换,改用 ref
const list = ref([1,2,3])
list.value = [4,5,6]
5. 还在用 Vue2 生命周期写法
坑点
<script setup> 没有 created、mounted 选项式写法,直接写会无效。
Vue3 组合式生命周期
import { onMounted, onUpdated, onUnmounted } from 'vue'
// 组件挂载完成
onMounted(() => {
// 接口请求、DOM 操作
})
// 组件更新完成
onUpdated(() => {})
// 组件销毁
onUnmounted(() => {})
顶层代码等价于 Vue2 的 created,无需额外写钩子。
6. 计算属性 computed 漏写 return、当作方法调用
坑点
// ❌ 无 return
const fullName = computed(() => {
firstName + lastName
})
// ❌ 错误调用
fullName()
正确写法
const fullName = computed(() => {
return firstName.value + lastName.value
})
// 直接取值 .value
console.log(fullName.value)
7. watch 监听嵌套对象不开启 deep
坑点
const user = reactive({ info: { age: 18 } })
// ❌ 监听不到嵌套属性变化
watch(user, () => {
console.log('变化了')
})
正确写法
// 监听嵌套对象,开启 deep
watch(user, () => {
console.log('变化了')
}, { deep: true })
// 监听 ref 不要加 .value
watch(count, (newVal) => {
console.log(newVal)
})
8. script setup 忘记导入 Vue API
坑点
直接使用 ref、reactive、computed、watch 报未定义。
常用必导入
import {
ref,
reactive,
computed,
watch,
onMounted,
toRefs,
defineProps,
defineEmits,
defineExpose
} from 'vue'
9. 父组件拿不到子组件的方法与变量
坑点
<script setup> 作用域封闭,子组件变量和方法默认不对外暴露,父组件 ref 访问不到。
子组件
<script setup>
const testFn = () => {
alert('子组件方法')
}
// 暴露给父组件
defineExpose({ testFn })
</script>
父组件
<Child ref="childRef" />
<script setup>
const childRef = ref(null)
const callChildFn = () => {
childRef.value.testFn()
}
</script>
10. 样式穿透还用 Vue2 的 /deep/ >>>
坑点
Vue3 中 /deep/、>>> 已失效,样式穿透不生效。
正确写法
/* Vue3 统一使用 :deep() */
:deep(.child-wrap .item) {
color: #f00;
font-size: 14px;
}
快速总结速记
基础类型用 ref,对象数组用 reactive script 中 ref 必加 .value 禁止直接修改 props,用 emit 通信 解构用 toRefs,避免响应式丢失 生命周期用 onMounted 等组合式 API computed 必须 return,不能加括号调用 监听嵌套对象配置 deep: true 所有 Vue API 手动导入 子组件对外暴露方法用 defineExpose 样式穿透统一用 :deep()