详解vue3 computed 不触发set函数

起因

前些天,之前的同事问我,他在用vue3 v-model绑定对象时,在子组件中改变对象的属性值不会触发set函数,但是在vue2中是可行的,问我咋回事。为了安全,我就不展示他的代码。他的代码逻辑大致是这样的。input 输入时,父组件的值会随着改变,但是无法获取到update事件。

js 复制代码
// 父组件
<script setup lang="ts">
import Child from './Child.vue'
import { reactive } from 'vue'

const propsReactive = reactive({})
const handleUpdatePropsReactive = () => {
  console.log('handleUpdatePropsReactive')
}
</script>

<template>
    <Child
      v-model:props-reactive="propsReactive"
      @update:props-reactive="handleUpdatePropsReactive"
    />
    <div>parentPropsReactive:{{ JSON.stringify(propsReactive) }}</div>
</template>
js 复制代码
// 子组件
<script setup lang="ts">
import { defineProps, computed } from 'vue'
const props = defineProps<{
  propsReactive: any
}>()
const emits = defineEmits<{
  (event: 'update:propsReactive', value: any): void
}>()

const handleInputPropsReactive = (event: any) => {
  _propsReactive.value.testa = event.target.value
}

const _propsReactive = computed({
  get() {
    return props.propsReactive
  },
  set(val) {
    emits('update:propsReactive', val)
  }
})
</script>

<template>
  childPropsReactive:<input @input="handleInputPropsReactive" :value="_propsReactive.testa" />
  <button
    @click="
      () => {
        _propsReactive = { testa: '' }
      }
    "
  >
    change instance
  </button>
</template>

好家伙,这能触发才怪😅😅😅

原因

vue3 的响应式是通过Proxy来实现的,他是对整个对象进行代理,我们在改变对象属性时,实际上是对这个代理对象的set操作,而computed的set 函数触发是需要对整个对象重新赋值才能触发的,就比如上面的button事件,将属性赋值成一个新的对象,才会触发computed的set 函数。但是,这种操作毫无意义,甚至会产生一些让你摸不着头脑的bug,比如,你在父组件中再次获取该值,发现不会变动,因为赋值成新对象之后,reactive就失去了响应性。那为什么Vue2 有效呢?Vue2 的响应式,深层有一个watcher对象,用来监视对象的改变,当属性发生改变时,会重新求职,然后将新的求职对象重新赋值给value,所以明白了吧🤣🤣🤣

改进

在父组件中,使用watch来替代@update事件。如下,

diff 复制代码
// 父组件
<script setup lang="ts">
import Child from './Child.vue'
import { reactive } from 'vue'

const propsReactive = reactive({})
-const handleUpdatePropsReactive = () => {
-  console.log('handleUpdatePropsReactive')
-}
+watch(propsReactive, (n) => {
+  console.log('propsReactive changed', n)
+})
</script>

<template>
    <Child
      v-model:props-reactive="propsReactive"
-     @update:props-reactive="handleUpdatePropsReactive"
    />
    <div>parentPropsReactive:{{ JSON.stringify(propsReactive) }}</div>
</template>

这里推荐v-model绑定的对象使用ref来定义,好处是,即使在子组件中重新赋值为一个新对象,父组件中也会保持数据的响应,而reactive则会失去响应性。

使用@update事件

如果还是想用@update事件,那么子组件中computed get函数需要返回一个代理对象,如下

缺点:不能触发父组件的watch监听,只能通过@update事件来处理对象的改变,但父组件依旧可以及时响应数据的变化。

diff 复制代码
//子组件

<script setup lang="ts">
import { defineProps, computed } from 'vue'
const props = defineProps<{
  propsRef: any
}>()
const emits = defineEmits<{
  (event: 'update:propsRef', value: any): void
}>()
const handleInputPropsRef = (event: any) => {
  _propsRef.value.testa = event.target.value
}

const _propsRef = computed({
  get() {
-    return props.propsRef
    // 返回Proxy代理对象
+    return new Proxy(props.propsRef, {
+      get(target, p, receiver) {
+        return Reflect.get(target, p, receiver)
+      },
+      set(target, p, newValue, receiver) {
+        emits('update:propsRef', {
+          ...target,
+          [p]: newValue
+        })

+        return Reflect.set(target, p, newValue, receiver)
+      }
+    })
  },
  set(val) {
    emits('update:propsRef', val)
  }
})
</script>

<template>
  childPropsRef: <input @input="handleInputPropsRef" :value="_propsRef.testa" />
  <button
    @click="
      () => {
        _propsRef = { testa: '' }
      }
    "
  >
    change instance
  </button>
</template>

很多人会问,为什么不会触发watch函数🧐🧐🧐?watch 监听的是propsRef.value 或者propsReactive,而我们在computed中,实际上是对proxy代理对象进行操作,他们并不是同一个对象。

相关推荐
宁雨桥几秒前
AI前端开发面试题分享
前端·人工智能·ai
亿元程序员几秒前
求求你别卷了,主管又转发你的文章到工作群了...我看了之后五味杂陈,决定卷个毛线!
前端
kyriewen1110 分钟前
你的前端滤镜慢得像PPT?用Rust+WebAssembly,一秒处理4K图
开发语言·前端·javascript·设计模式·rust·ecmascript·powerpoint
人道领域18 分钟前
【LeetCode刷题日记】二叉树层序遍历完全指南:从基础到LeetCode实战一篇搞定BFS模板,秒杀4道经典面试题
java·开发语言·数据结构·leetcode·面试·二叉树
QD_ANJING21 分钟前
建议5月的Web前端开发都去飞书上准备面试...
前端·人工智能·面试·职场和发展·前端框架·状态模式·ai编程
研究点啥好呢28 分钟前
面馆开业!客官,你的面(经)好了!
python·阿里云·docker·面试·reactjs·求职招聘·react
萤萤七悬28 分钟前
【人工智能训练师3级】考试准备(2026)三、实操题1.1.3-3.2.5
前端·数据库·人工智能
yqcoder33 分钟前
JavaScript 深拷贝:如何彻底切断引用关联?
开发语言·前端·javascript
镜宇秋霖丶9 小时前
2026.5.6@霖宇博客制作中遇见的问题
前端·javascript·vue.js