🔥🔥🔥Vue3 最大深坑:解构 props 后,页面刷新 10 次都不更新!

114 阅读3分钟

标签: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 不行?

技术栈实现机制解构后是否响应
ReactuseState 返回新数组,每次渲染重新赋值✅ 安全
Vue3依赖 Proxy 拦截 . 操作❌ 一次性读值,丢失引用

4. 保持响应式的 4 种姿势

场景代码示例备注
直接读取props.user.name最保险,就是啰嗦
整 props 转 refconst 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>

要点

  1. 永远只改 form 这个 computed,不要直接改 props.modelValue
  2. 父组件用 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,大列表会额外创建大量响应式引用;
如果组件仅消费少量字段,优先用 toRefcomputed 精准监听,减少内存开销。


8. 面试快问快答

Q1:Vue3 解构 props 会咋样?
A:丢失响应式,因为解构是“读快照”,脱离 Proxy 拦截。

Q2:那 React 为什么可以?
A:React 每次渲染重新执行函数,重新赋值;Vue 依赖持续引用。

Q3:只想解构一次但又想响应,怎么办?
AtoRefs(props)toRef(props,'key')

Q4:表单场景最佳实践?
A:用 computedget/set 封装,内部改 computed,回抛 update:modelValue


9. 一句话总结

“props 是代理,解构即快照;要拆用 toRefs,表单包 computed。”

把这句口诀贴在工位,以后再也不用 console.log 怀疑人生了。


如果本文帮到你,欢迎 点赞/ 分享
有更多 Vue 响应式奇淫技巧,评论区见!