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

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 只是一个引用,而不是一个响应式引用本身。在这种情况下,你应该通过触发一个事件来通知父组件进行更新。

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

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

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

  • 本文向大家介绍Vue3 响应式侦听与计算的实现,包括了Vue3 响应式侦听与计算的实现的使用技巧和注意事项,需要的朋友参考一下 响应式侦听和计算 有时我们需要依赖于其他状态的状态——在 Vue 中,这是用组件 计算属性 处理的,以直接创建计算值,我们可以使用 computed 方法:它接受 getter 函数并为 getter 返回的值返回一个不可变的响应式 ref 对象。 我们先来看看一个简单的

  • vue3组合式api+ts,props嵌套传递,没有用变量接收,直接绑定到元素上,当祖父级数据更新,父级和子级都会跟随响应式变化嘛? 祖父级给父级组件绑定选中行的数据,如下: 父级组件直接将props.data绑定给子级组件,如下: 当祖父级数据更新,父级和子级都会跟随响应式变化嘛? 我知道当后代组件用变量接收props传递数据时,需要侦听props,才能响应式,这种直接props直接绑定到元素上

  • 我试图用制作一个响应线图。 我用这个MWE重现了我的尝试: 如果我让浏览器更宽,线条图会像你期望的那样响应,填满空间。 我的问题是,如果我使浏览器变窄,图形不会像我使其变宽时那样调整大小以填充浏览器宽度的100%。对于新的浏览器宽度来说,它仍然太宽。有趣的是,在刷新时,图形总是占据浏览器的100%。 把浏览器做宽做窄,如何让图占据浏览器的100%?

  • 如果说集合是 Meteor 的核心功能,那么响应式可以能让这个核心功能更强大。 集合从根本上改变你的应用程序的数据处理方式。从而不必手动检查数据更改(例如,通过一个 AJAX 调用),再根据这些变化去修改 HTML 页面,Meteor 可以随时检测到数据的更改,并将它无缝地应用到你的用户界面上。 让我们思考一下:在后台,当底层的数据集合被更新以后, Meteor 能够马上修改用户界面的任何部分。

  • 一、整个图表响应式 默认情况下,Highcharts 图表都是支持整个图表跟随图表容器响应式的,无需额外配置,只需要保证容器的宽度自适应即可,下面是最简单的容器样式设置 <!-- 通过设置 minwidth 保证 div 容器自适应,那么 Highcharts 图表就会跟随容器自适应 --> <div style="height:400px;min-width:300px"></div> 另外