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

作者站长头像
站长
· 阅读数 11

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>
回复
1个回答
avatar
test
2024-07-04

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

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


这里涉及的就是 @陟上晴明 提到的 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;
  }
})

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

回复
likes
适合作为回答的
  • 经过验证的有效解决办法
  • 自己的经验指引,对解决问题有帮助
  • 遵循 Markdown 语法排版,代码语义正确
不该作为回答的
  • 询问内容细节或回复楼层
  • 与题目无关的内容
  • “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容