Vue3组件通信“不刷新”问题

82

小伙伴们在写Vue3项目时,是不是常遇到这种情况:组件之间传数据,数据明明改了,页面却纹丝不动?明明控制台打印数据是新的,界面就是不跟更,简直让人挠头!其实呀,这不是bug,大概率是咱们没摸透Vue3的“响应式套路”,今天就用唠嗑的方式把这事讲明白~

先搞懂:为啥数据变了页面不刷新?

Vue3靠“响应式系统”驱动页面更新,简单说就是:只有被Vue“盯紧”的数据,改了才会通知页面刷新。要是数据没被“盯”上,哪怕你改得再欢,页面也懒得理你。而组件通信时的不刷新,基本都绕不开这俩坑:要么是传的非响应式数据,要么是修改方式不对,把响应式给“玩坏”了。

常见场景+踩坑解决,手把手教你避坑

场景1:父传子,子组件拿不到最新值

父组件给子组件传数据,这是最基础的操作吧?但有时候父组件数据改了,子组件却还是老样子,比如这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 父组件 Parent.vue --> <template> <Child :user="user" /> <button @click="changeUser">改名字</button> </template> <script setup> import Child from './Child.vue' let user = { name: '张三' } const changeUser = () => { // 踩坑:直接赋值新对象,子组件没反应 user = { name: '李四' } } </script>

这时候点按钮,父组件的user确实变了,但子组件就是不刷新。为啥?因为最开始的user虽然是响应式的,但咱们直接给user“换了个对象”,新对象没被Vue的响应式系统“注册”,子组件自然没收到通知。

✅ 解决办法:别直接换对象,改对象里的属性;或者用ref包裹对象(更稳妥)

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 父组件 正确写法 --> <script setup> import Child from './Child.vue' import { ref } from 'vue' // 用ref包裹,不管是改属性还是换对象都没问题 let user = ref({ name: '张三' }) const changeUser = () => { // 方式1:改属性 user.value.name = '李四' // 方式2:换对象(ref包裹后,这样也能响应) // user.value = { name: '李四' } } </script>

场景2:子传父,父组件数据变了但页面没更

子组件通过emit给父组件传值,父组件接收后数据改了,页面却没反应。这种情况大概率是父组件“手动存值”时,把响应式弄丢了。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- 子组件 Child.vue --> <template> <button @click="sendMsg">传消息给父组件</button> </template> <script setup> const emit = defineEmits(['send-msg']) const sendMsg = () => { emit('send-msg', '我是子组件的消息') } </script> <!-- 父组件 Parent.vue 踩坑写法 --> <template> <Child @send-msg="handleMsg" /> <p>{{ msg }}</p> </template> <script setup> import Child from './Child.vue' // 踩坑:msg不是响应式的! let msg = '' const handleMsg = (newMsg) => { msg = newMsg // 数据变了,但页面不刷新 } </script>

一目了然吧?父组件的msg就是个普通变量,没有用ref或reactive包裹,改了自然不会触发刷新。

✅ 解决办法:给接收数据的变量加“响应式外套”,用ref包基本类型,用ref/reactive包复杂类型

1
2
3
4
5
6
7
8
9
10
<!-- 父组件 正确写法 --> <script setup> import Child from './Child.vue' import { ref } from 'vue' // 用ref包裹,变成响应式变量 let msg = ref('') const handleMsg = (newMsg) => { msg.value = newMsg // 改完页面就刷新啦 } </script>

场景3:兄弟组件通信,数据传了但页面不更

兄弟组件之间没直接关系,一般用“事件总线”或“Pinia状态管理”传数据。这里先讲简单的事件总线(Vue3里要自己封装哦),踩坑点还是“响应式”。

比如咱们封装个简单的事件总线:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// eventBus.js import { ref } from 'vue' const bus = ref(new Map()) export const useEventBus = () => { const on = (name, callback) => { bus.value.set(name, callback) } const emit = (name, ...args) => { const callback = bus.value.get(name) callback && callback(...args) } return { on, emit } }

要是兄弟A传数据给兄弟B时,B组件接收后存到了非响应式变量里,照样不刷新。所以核心还是:接收数据的变量必须是响应式的!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 兄弟A组件 --> <script setup> import { useEventBus } from './eventBus.js' const { emit } = useEventBus() const sendToB = () => { emit('brother-msg', '来自A的消息') } </script> <!-- 兄弟B组件 正确写法 --> <script setup> import { ref } from 'vue' import { useEventBus } from './eventBus.js' const { on } = useEventBus() // 响应式变量存数据 let msg = ref('') on('brother-msg', (newMsg) => { msg.value = newMsg // 页面会刷新 }) </script>

场景4:Pinia状态变了,组件不刷新

用Pinia管理全局状态时,也可能遇到“状态改了页面不更”的情况,多半是这俩问题:

  1. 直接修改Pinia的原始数据,没走响应式:比如在Pinia里定义了个对象,组件里直接给对象赋值新的,没改属性也没用水合(hydrate)。

  2. 组件里“解构”状态时,把响应式弄没了:比如用const { count } = useStore(),这样count就变成普通变量了,改了Pinia里的状态,组件也收不到。

✅ 解决办法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Pinia仓库 store.js import { defineStore } from 'pinia' export const useMyStore = defineStore('myStore', { state: () => ({ user: { name: '张三' }, count: 0 }), actions: { // 改状态最好用actions,更规范 changeName(newName) { this.user.name = newName // 改属性,响应式正常 // 或者换对象:this.user = { name: newName } }, addCount() { this.count++ } } })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 组件里正确使用 --> <script setup> import { useMyStore } from './store.js' import { storeToRefs } from 'pinia' // 关键:用这个解构响应式 const store = useMyStore() // 错误写法:解构后失去响应式 // const { count, user } = store // 正确写法1:直接用store.xxx console.log(store.count) // 正确写法2:用storeToRefs解构,保留响应式 const { count, user } = storeToRefs(store) // 修改状态 const change = () => { store.changeName('李四') // 调用actions改 store.addCount() } </script>

最后总结个“万能避坑指南”

  1. 数据要“响应式”:基本类型(字符串、数字等)用ref,复杂类型(对象、数组)用ref或reactive,别用普通变量;

  2. 修改要“按规矩来”:ref包裹的改.value,reactive包裹的改属性(别直接换对象),Pinia状态用actions改或storeToRefs解构;

  3. 接收要“保响应”:不管是父传子、子传父还是兄弟通信,接收数据的变量必须是响应式的,别直接存到普通变量里。

其实Vue3的响应式没那么玄乎,核心就是让数据被Vue“盯牢”。下次再遇到不刷新的问题,先检查下数据是不是响应式的,修改方式对不对,基本都能解决~

目录