vue3 阻止子组件对父组件 props 的响应式修改

props 是单向数据流,我需要在 props 修改的时候,拿到传递进去的 props 值的拷贝,并在子组件中将其变成响应式的,但我将其变成响应式时,反而修改了父组件的值,代码如下

const props = defineProps<{
  formValue: Bug;
}>();

// props.formValue 是父组件响应式对象的 value
// 无法被 watch 监听
const { formValue } = toRefs(props);

let data = ref<Bug>({} as Bug);
const updateData = () => {
  data.value = props.formValue;
  // 最后我使用 JSON 组合拳实现了拷贝,但我觉得在 vue3 中多少有点离谱
  // 因为 vue2 都不会有这种问题
  // data.value =  JSON.parse(JSON.stringify(props.formValue));
};

watch(formValue, updateData);

顺便说一下这个使用场景,我有一个 Dialog 组件,里面封装好了一个表单,父组件传对象给 Props,然后子组件拿到后在组件内部修改,修改过程不会影响父组件中原有对象,Dialog 提交表单后,会通过 emit 的方式返回修改的值

最近刚用上 vue3 还不是很懂

阅读 10k
3 个回答

直接 watch 组件的 props 对应的属性即可,props 本身是一个 reactive,如果某个属性是个对象,它会被 Proxy 深度代理,想要防止相同的引用则需要深拷贝(如果确定只有一层浅拷贝即可)

import { watch } from 'vue'

const props = defineProps<{
  formValue: Bug;
}>()

watch(() => props.formValue, updateFormValue)

function updateFormValue(value: Bug) {
  // 合并逻辑,想脱离相同的引用需要拷贝
}

我本意是想将一个表单组件封装起来,可以重复利用,所以会从父组件传递一个 props 给子组件,假设这个 props 是父组件的某个数据对象 ob

ps: 这种场景其实很常见,比如简历填写场景,用户信息的表单,可以在个人中心用到,也可以在提交简历的时候用到

由于 vueprops 其实一个单向数据流,但作为一个表单组件,不可避免的会修改 ob 的值,似乎这两者成为矛盾,因为不传递 props,就需要将子组件复制粘贴多份,无法复用这个表单逻辑,传了子组件拿到后也无法修改,后面对种情况我思索出了两种解决方法

  1. 表单组件(业务组件),可以使用 vuex/pinia 去共享数据,这种数据交互要比 props 传递方便得多,而且不容易出错,所以表单组件父组件之间的数据交互应该统一使用 vuex/pinia
    优点:

    1. 简单方便
    2. 数据变化可追溯

    缺点:

    1. 若只是一个很小的数据,引入 vuex/pinia 没必要(vue3 可以考虑自己实现一个 vuex/pinia 很简单,只需几行代码)
  2. vue2mixinvue3composition APImixin 不解释,自己去看文档,vue composition API 太笼统,解释一下,就是去利用 vue 解构出来的 ref/reactive表单组件的逻辑抽取出来,和 mixin 的目的一致(不要被 composition API 的名字误导太多)
    优点:

    1. 无需使用 vuex/pinia

    缺点:

    1. 没有封装组件,脱离了原本的目的,无法复用模板,只能复用逻辑,而我的表单组件往往逻辑简单,而模板 HTML 内容更多

总结

综上考虑,我选择了第一种方案,封装组件,使用 pinia 交互数据,第二种方案的复用不适合我(逻辑复用其实也可以用在封装组件当中,更加 DRYwin!),过去我总是纠结与组件与组件交互就应该使用 props,在 vue2vue3 的项目中也曾多次考虑过这个问题,因为 vuex 的大小,功能的复杂和难以操作的 API,总是让只是想简单的封装一个组件的我被一次次击退

举个例子,我流量用的不多,所以只是想要一个日租卡,但你偏要跟我说月租卡从长久来看更优惠,vuex 就是这样,项目大用它没问题,但我不需要这么多,总结 vuex 给的太多

但是 vue3 的响应式解构,以及 pinia 极小的体积,让我放下了 vuex 这种数据管理模式的偏见,在项目中大规模的使用

后期使用 vuex/pinia 也证明了它是可行的,从 props 传递一个表单数据对象给子组件去修改,是不被官方文档认可,也是实际上非常难以去维护的方案

PS: 珍爱生命,快用 vuex

vue 中 props 不是一个单向数据流吗?我也很奇怪这里为什么子组件可以直接修改父组件数据。是因为 vue 对复合数据没有做拷贝处理的原因吗?希望大佬能解答下。

目前我是通过 JSON.parse(JSON.stringify(newVal)) 转换了下,但是感觉这样不是太合理。

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