为什么 Vue 3 toRaw on props 保持反应性?

Why Vue 3 toRaw on props keeps reactivity?

我有一个场景,我想失去子组件中传入道具的反应性以修改道具,但是,我还想在道具更改时保持子组件更新(保持这个反应性的一部分)。这对于 forms/states 非常有用,其中数据更改可以是 cancelled/reverted.

一种方法是复制道具。在查找了最佳方法后,我决定使用 toRaw().

当我测试代码时,我发现了一些奇怪的行为。

场景 1. 如果 props 被传递给 toRaw() 函数并分配给一个新变量,它们可以在 Child 组件中修改并保持反应性。 - 这就是我想要的,因为控制台中没有警告,道具不会更改父数据,当父数据发生变化时,道具会更新。

场景 2。如果 props 在传递给 toRaw() 函数时被破坏性地分配,它们将失去反应性。

场景 3. 如果将 props 传递给 ref() 函数并分配给新变量,它们将失去反应性。

所以我的问题是:

// App.vue
<script setup>
import { ref } from 'vue'
import Comp from "./Comp.vue";
  
const name = ref("John");

const nameChanged = () => {
  name.value = "Jane " + Math.random();
  console.log(name.value);
}
</script>

<template>
  {{ name }}
  <Comp @changeName="nameChanged" :name="name"></Comp>
</template>

// Comp.vue
<script setup>
import {ref, toRaw, isRef} from "vue";
const props = defineProps(['name'])

var _props = toRaw(props);
var n = ref(props.name);
_props.name = "it workss";
const isPropRef = isRef(_props);
</script>

<template>
  <div>
    <h1>
        {{ _props.name }}, {{n}}
    </h1>
    <h1>
        isPropCopyRef: {{ isPropRef }}
    </h1>
    <button @click="$emit('changeName')">
        Change name
    </button>
  </div>  
</template>

Vue SFC link

why scenario 1 works as intended?

我认为 答案回答了这个问题

why isRef() is telling me that the _props copy is not reactive (but it is)?

isRef helper checks if the object passed is created with ref but the props are like an object created with reactive (also exists a isReactive 帮手)

is this the right approach to achieve my required flow?

不,toRaw 助手不复制代理,所以你仍然通过引用改变 props 对象,但是你不会在 vue devTools 中看到它,在控制台上看到警告,或者在DOM 因为你从 vue 中删除了 Proxy 触发器,你直接改变了原始对象,这是一种不好的做法,因为它会导致意外行为和调试困难。

您可以使用不同的方法来实现您想要的。您可以使用 watch, or writable Computed

我给你举个例子,使用Writable computed,但也适用于watch,原理是一样的,实践不变性原则,使用事件改变父数据​​

Note: my example is thinking that you are going to use this component with 'v-model', if not your case just change the props and events, the important part is the computed

import { computed } from 'vue';

const props = defineProps({
  modelValue: {
    type: Object, // assuming you have multiples properties in an form
    required: true,
  }
})

const emit = defineEmits(['update:modelValue'])

const formData = computed({
  get: () => props.modelValue,
  set: (newValue) => emit('update:modelValue', newValue),
})