当前位置: 首页 > 知识库问答 >
问题:

vue3 - Vue3: 响应式 props 解构得到的变量将不是响应式?也不会更新?

松俊才
2024-07-03

Vue的 响应性语法糖 文档中 响应式 props 解构 部分提到了:

.value 类似,为了保持响应性,你始终需要以 props.x 的方式访问这些 prop。这意味着你不能够解构 defineProps 的返回值,因为得到的变量将不是响应式的、也不会更新。

真的是这样吗?解构 defineProps 的返回值得到的变量将不是响应式,也不会更新。


例子1 父组件响应式数据改变 => 子组件解构出来的 proxy 对象

  • 父组件每隔一段时间就更新响应式数据,在子组件的 onUpdated 钩子中总是可以看到更新后的数据
  • 就算我们给响应式数据的value重新赋值一个值,在子组件的 onUpated 钩子中也总是可以看到更新后的数据

�� Vue SFC Playground

// Parent.vue
<script setup>
import Child from "./Child.vue"
import { onMounted, ref } from "vue";
const person = ref([
  { name: "peter", age: 18 },
  { name: "tom", age: 20 }
])
let cnt = 0;
setInterval(() => {
  person.value[0].name += cnt;
  ++cnt;
}, 2000)

function update() {
  person.value = []
}
</script>
<template>
  <Child :person="person" />
  <button @click="update()">update person.value</button>
</template>
//Child.vue
<script setup>
import { onMounted, onUpdated } from "vue";

const { person } = defineProps(["person"]);

onMounted(() => {
  console.log("mounted person: ", JSON.stringify(person));
})

onUpdated(() => {
  console.log("updated person: ", JSON.stringify(person));
})
</script>
<template>
  <div v-for="item in person" class="flex gap-2 items-center">
    <span>{{item.name}}</span>
    <span>{{item.age}}</span>
  </div>
</template>
<style scoped>
.flex {
  display: flex;
}
.gap-2 {
  gap: 10px;
}
.items-center {
  align-items: center;
}
</style>

例子2:改变子组件解构出来的 proxy => 父组件的响应式数据

  • 子组件每隔一段时间就更新响应式数据,在父组件的 onUpdated 钩子中总是可以看到更新后的数据。在视图上我们也可以看到父组件上的响应式数据和子组件上解构出来的 proxy 在一起变化。

�� Vue SFC Playground

//Parent.vue
<script setup>
import Child from "./Child.vue"
import { onMounted, onUpdated, ref } from "vue";
const person = ref([
  { name: "peter", age: 18 },
  { name: "tom", age: 20 }
])

onMounted(() => {
  console.log("mounted person: ", JSON.stringify(person.value));
})

onUpdated(() => {
  console.log("updated person: ", JSON.stringify(person.value));
})
</script>
<template>
  <div v-for="item in person" class="flex gap-2 items-center">
    <span>{{item.name}}</span>
    <span>{{item.age}}</span>
  </div>
  <div>*****************************************************</div>
  <Child :person="person" />
</template>
<style scoped>
.flex {
  display: flex;
}
.gap-2 {
  gap: 10px;
}
.items-center {
  align-items: center;
}
</style>
//Child.vue
<script setup>
import { onMounted, onUpdated } from "vue";

const { person } = defineProps(["person"]);

let cnt = 0;
setInterval(() => {
  person[0].name += cnt;
  ++cnt;
}, 2000)

function update() {
}
</script>
<template>
  <div v-for="item in person" class="flex gap-2 items-center">
    <span>{{item.name}}</span>
    <span>{{item.age}}</span>
  </div>
  <button @click="update()">update person.value</button>
</template>
<style scoped>
.flex {
  display: flex;
}
.gap-2 {
  gap: 10px;
}
.items-center {
  align-items: center;
}
</style>

共有3个答案

巢宏富
2024-07-03

emmmmm……确实好像一直没有关注过 响应性语法糖 这个功能。但是在文档开头有提示:

已移除的实验性功能

响应性语法糖曾经是一个实验性功能,且已在最新的 3.4 版本中被移除,请阅读废弃原因。

所以我想既然废弃了,那么社区应该会有相关的讨论,同时应该也会有 defineProps 一些改进。
果然 �� prop sugar · vuejs/rfcs · Discussion #394

这个 Feat 已经在 v3.2.20 被合并。

商骞仕
2024-07-03

所谓“丢失响应性”,它指的是这种情况:��传送门��。

注意组件一和组件二在解构写法上的区别。


这里涉及的就是 @陟上晴明 提到的 Props Destructure Transform 这一特性:

const { foo, bar } = defineProps(['foo', 'bar']);

这种写法,也和原题中的代码写法是一样的,即直接对 defineProps 的返回值解构。这种情况下父组件改变传入的 props,子组件也能获得最新的值。

但你依然不能这么写:

const props = defineProps(['foo', 'bar']);
const { foo, bar } = props;

注意这里不是直接解构 defineProps 的返回值,而是先用一个中间变量存储、然后再解构这个中间变量。这种情况下父组件再改变 props,子组件就不会得到最新的值了。因为 Props Destructure Transform 这个特性是发生在编译阶段的,Vue 会捕获 script setup 里对 defineProps 的解构;而后面这种写法的解构实际发生在运行阶段,Vue 捕获不到。

而所谓“丢失响应式”,针对的也是后一种这种写法。(当然了,没这个特性之前,前一种写法也一样丢失)


P.S. 多补充一点,为啥文档里要强调 props 的解构会失去响应式,是因为除了 script setup 以外,还有一种最常见的错误写法是这样的:

export default defineComponent({
  props: ['foo', 'bar'],
  setup(props) {
    const { foo, bar } = props;
  }
})

为啥是错误写法应该不难理解,就不解释了。

山寒
2024-07-03

答案

在 Vue 3 中,当使用 defineProps 解构 props 时,得到的变量 person 实际上是响应式的,因为它仍然是对原始 prop 的引用。这意味着,当父组件中的响应式数据改变时,子组件中解构出来的 person 也会相应地更新。

在你的例子中,无论是父组件更新 person.value 还是子组件修改 person[0].name,这些更改都会反映在两个组件的视图中,因为 person 在两个组件中都是对同一个响应式引用的访问。

不过,有一个需要注意的是,尽管解构得到的 person 是响应式的,但在子组件中直接修改它(比如通过 person[0].name += cnt;)可能不是一个好的做法,因为这可能会绕过 Vue 的响应式系统。在 Vue 3 中,推荐使用 Vue 提供的响应式方法来修改状态,比如使用 reactiveref 创建的响应式对象或引用,并通过它们提供的 API(如 .value 或直接修改对象属性)来更新状态。

在你的例子中,由于 person 是一个数组,而数组元素本身也是对象,因此你可以直接修改对象属性而不需要担心响应性问题,因为对象的属性本身就是响应式的。但是,如果你尝试在子组件中重新赋值 person(比如 person = [...]),那么这将不会触发父组件的更新,因为 person 只是一个引用,而不是一个响应式引用本身。在这种情况下,你应该通过触发一个事件来通知父组件进行更新。

 类似资料:
  • vue3 响应式无法更新 下面是最小复现代码 https://play.vuejs.org/#eNqVVs2O2zYQfhVWF2kBV27QnlzbaJIu2vTQD... 点击“修改值”这个按钮,最上层组件能够更新值, 但是最内层的组件无法监听到值被改变了

  • 如题,定义一个组件,设置 props 如下: 当我直接定义 的时候,会提示这样会失去响应性 所以想问下,如何可以将这样的定义,转为响应性

  • 在vue3框架中,我使用ref定义了一个变量,但是发现在更新其数据之后,页面上并不会有响应式变化,具体表现是在更新数据之后不会出现表格最前面的选择框 数据定义 数据初始化,其中list是一个数组,具有唯一的id属性 组件A中定义了计算属性rowSelection 当点击一个按钮之后,触发BatchEdit函数,通过emit抛出新的rowSelection 在父组件中进行事件定义并更新内容 rowS

  • 例如我创建了一个dataHook main.ts中 页面有个按钮就执行v.value += 1,为什么console.log还是原值1呢?

  • Vue3 vue-I18u 切换语言无法响应式变化 版本�� "vue": "^3.3.4", "vue-i18n": "9", 当切换时修改 全局locale 只响应式变化html中的{{$t('')}} script中的$t() 不会变化 希望只要使用到$t的地方都能变化

  • 为什么响应式数据变了,但是视图没有更新呢? 问题背景 没有后端,简单模拟了一下后端的数据和操作。 最开始的时候,加载服务端数据到本地,并将其转换为一个响应式数据(这样我们就可以操作这个响应式数据来自动更新表格显示了) 本地编辑修改数据 模拟将修改内容上传到服务端,服务端修改数据库(先删除原来的,然后添加编辑过后的)和结果提示 问题详情 编辑修改之后,点击获取最新数据按钮,表格中的数据并没有发生变化