Vue 3中Object.assign与响应式数据?

前端小白,在学习Vue 3 响应式数据的时候,遇到了一个问题

<script lang="ts">
  export default{
    name:"test",
  }
</script>
<script lang="ts" setup>
  import {toRefs,ref,reactive} from "vue";

  let person = reactive({aa:"abc",bb:123})

  function c(){
    console.log(1)
    console.log(person)

    Object.assign(person,{aa:"121231",bb:12131})

    person =  {aa:"hhh",bb:13}   
    person =  {aa:"hh",bb:132} 
    
    console.log(person)
  }
  
</script>
<template>
  <span>姓名:{{person.aa}} </span> <br>

  <button @click="c">点击修改数据</button>

</template>

vue3官方文档中有如下说明
image.png

对于上面的代码和文档描述,按理说结果是姓名:121231,但是实际运行结果是姓名:hh

如果Object.assign(person,{aa:"121231",bb:12131})不存在,或者说不是先执行,确实是无法在页面上修改数据为hh,但是只要这一行代码存在并且先执行,就会修改为hh

根据上述现象,初步判断可能是Object.assign(person,{aa:"121231",bb:12131})代码副作用,查了MDN,说该API会调用get/set方法。

image.png

虽然vue是通过监听set/get方法实现的响应式渲染,上述mdn内容只是说明了Object.assign(person,{aa:"121231",bb:12131})为什么有效,但是还是不能解决我的疑惑

希望大佬说一下原因

阅读 2.5k
avatarAI BotBETA

你的问题主要在于理解Vue 3的响应式系统和Object.assign如何交互。

Vue 3使用了一个新的响应式系统,该系统基于Proxy对象,而不是之前版本中的Object.defineProperty。当你在Vue组件中创建一个响应式对象时,Vue实际上会返回一个特殊的Proxy对象,该对象拦截了读取和设置属性的操作。当属性被设置时,Vue会知道该属性已经被修改,并重新渲染依赖于该属性的部分。

现在,让我们看看Object.assign是如何工作的。这个函数会遍历源对象的所有可枚举属性,并将它们复制到目标对象中。如果目标对象已经有了同名属性,那么这些属性将被源属性覆盖。

在你的代码中,Object.assign(person,{aa:"121231",bb:12131})会将{aa:"121231",bb:12131}这个对象的所有属性复制到person对象中。由于person是一个Vue响应式对象,当它的属性被设置时,Vue会知道这些属性已经被修改,并重新渲染依赖于这些属性的部分。

然而,你需要注意的是,Object.assign并不会返回一个新的对象,而是返回目标对象。在你的代码中,person就是目标对象。因此,当你执行person = {aa:"hhh",bb:13}person = {aa:"hh",bb:132}时,你实际上是在改变person的引用,而不是它的属性。由于Vue不会检测到对象引用的改变(只有当一个响应式对象的引用被改变时,Vue才会标记它为“dirty”),因此Vue不会重新渲染依赖于person的任何部分。

这就是为什么你看到的结果是“姓名:hh”,而不是“姓名:121231”。如果你希望在改变对象属性时能够触发Vue的重新渲染,你应该直接设置对象的属性,而不是改变对象的引用。例如,你可以使用解构赋值来更改对象的属性:

person = { ...person, aa: "hh", bb: 132 };

在这个例子中,由于我们没有改变person的引用,只有它的属性被改变,因此Vue会检测到这个变化并重新渲染依赖于person的任何部分。

3 个回答

造成这个现象的原因是 Object.assign 修改了 person 的属性,触发了组件更新,从编译完成后的代码我们可以看出,template 最终被编译成了 render function,其中引用了 person.aa,也就是说 render function 会被收集为 person.aa 的依赖

Object.assign(person,{aa:"121231",bb:12131}) 被执行,也就意味着 render function 会被重新执行,但这里很重要的一点是,vue的更新是异步的,状态变化驱动的更新会被合并到下一个 nextTick 执行

综上,这里的执行顺序其实是

  1. Object.assign(person,{aa:"121231",bb:12131})
  2. person = {aa:"hhh",bb:13}
  3. person = {aa:"hh",bb:132}
  4. ...
  5. render function
    image.png

这就是为什么步骤3中 person = {aa:"hh",bb:132} 能影响渲染结果,但重新赋值也导致其丢掉了响应性

person =  {aa:"hhh",bb:13}   
person =  {aa:"hh",bb:132}

这里开始 person 这个对象已经失去响应式结构了

这跟 Vue 无关,这是语法问题。

const a = ref(0); // <-- 创建一个 Ref 对象,value 是 0,然后用 a 指向这个变量
a.value = 1; // 将 a 的 value 修改为 1,a 仍然有响应式
a = 2; // <-- 创建一个常量,值为 2,然后把 a 指向这个常量。a 不在有响应式,因为常量 2 没有响应式

所以,因为 Object.assign 修改的是目标对象的某个键上的值,所以不影响响应式。而赋值直接就把变量指向的目标改变了,所以就没有响应式了。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏