标签:Vue3、响应式、props、toRefs、前端踩坑
预计阅读:8 分钟
含可运行 Demo,文末附「面试快问快答」
1. 一句鬼故事:解构即“快照”
在 <script setup> 里写下这行代码时,响应式就已经悄悄“阵亡”:
const { title } = defineProps(['title']) // ❌ title 永远是字符串快照
Vue3 的响应式依赖 Proxy;解构等价于一次性“读值”,拿到的只是普通变量,后续再怎么改 props,模板都不会更新。
2. 真实场景复现(可本地跑)
2.1 父组件
<!-- Parent.vue -->
<template>
<button @click="title = 'Hello ' + Date.now()">改标题</button>
<Child :title="title" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const title = ref('Initial')
</script>
2.2 子组件——错误写法
<!-- Child.vue -->
<script setup>
const { title } = defineProps(['title']) // ❌ 响应式丢失
</script>
<template>
<h1>{{ title }}</h1> <!-- 点按钮不会变 -->
</template>
2.3 子组件——正确写法
<script setup>
const props = defineProps(['title'])
</script>
<template>
<h1>{{ props.title }}</h1> <!-- ✅ 会变 -->
</template>
3. 为什么 React 可以解构,Vue 不行?
| 技术栈 | 实现机制 | 解构后是否响应 |
|---|---|---|
| React | useState 返回新数组,每次渲染重新赋值 | ✅ 安全 |
| Vue3 | 依赖 Proxy 拦截 . 操作 | ❌ 一次性读值,丢失引用 |
4. 保持响应式的 4 种姿势
| 场景 | 代码示例 | 备注 |
|---|---|---|
| 直接读取 | props.user.name | 最保险,就是啰嗦 |
| 整 props 转 ref | const p = toRefs(props) | 每个属性都是 Ref,可解构 |
| 仅单个属性 | const title = toRef(props,'title') | 只想要一个 |
| 派生计算值 | const full = computed(()=> props.first + props.last) | 只读场景 |
注意:
toRefs/toRef要从'vue'引入,不能解构嵌套对象:// ❌ 错误 const { name } = toRefs(props.user) // ✅ 正确 const user = toRefs(props.user) const name = user.name
5. 高频实战:v-model 表单封装
需求:把 props.modelValue 拆成两个输入框,同时回传父组件。
❌ 错误示范(直接解构)
const { username, email } = props.modelValue // 改完表单父组件无感知
✅ 官方推荐写法(computed 封装)
<script setup>
const props = defineProps({ modelValue: Object })
const emit = defineEmits(['update:modelValue'])
const form = computed({
get: () => props.modelValue,
set: val => emit('update:modelValue', val)
})
</script>
<template>
<input v-model="form.username" />
<input v-model="form.email" />
</template>
要点
- 永远只改
form这个 computed,不要直接改props.modelValue。 - 父组件用
v-model:user="user"即可,无需额外监听事件。
6. 进阶:ESLint 提前防错
安装官方规则包:
npm i -D eslint-plugin-vue
在 .eslintrc 里加:
{
"rules": {
"vue/no-destructuring-props": "error"
}
}
效果:只要你在 <script setup> 里解构 defineProps,直接红线报错,CI 止步。
7. 性能陷阱:toRefs 别乱用
toRefs 会把所有属性都转成 ref,大列表会额外创建大量响应式引用;
如果组件仅消费少量字段,优先用 toRef 或 computed 精准监听,减少内存开销。
8. 面试快问快答
Q1:Vue3 解构 props 会咋样?
A:丢失响应式,因为解构是“读快照”,脱离 Proxy 拦截。
Q2:那 React 为什么可以?
A:React 每次渲染重新执行函数,重新赋值;Vue 依赖持续引用。
Q3:只想解构一次但又想响应,怎么办?
A:toRefs(props) 或 toRef(props,'key')。
Q4:表单场景最佳实践?
A:用 computed 做 get/set 封装,内部改 computed,回抛 update:modelValue。
9. 一句话总结
“props 是代理,解构即快照;要拆用 toRefs,表单包 computed。”
把这句口诀贴在工位,以后再也不用 console.log 怀疑人生了。
如果本文帮到你,欢迎 点赞/ 分享;
有更多 Vue 响应式奇淫技巧,评论区见!