likes
comments
collection
share

vue3如何进行真正意义上的父组件与子组件的双向绑定

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

这里提供两种方法,父子组件如下 父组件FatherComponent,子组件ChildrenComponent

//父组件
<FatherComponent v-model"data" />


//子组件
<el-input
  v-model="inputValue"
/>

...
const props = defineProps({ data: String })
const inputValue = ref(props.modelValue || '')

方法一 利用computed

//父组件
<template>
  <div>
    <ChildrenComponent v-model="data"></ChildrenComponent>
  </div>
</template>

<script>
import { ref } from 'vue'
import ChildrenComponent from './ChildrenComponent.vue'
export default {
  name: "FatherComponent",
  components: {
    ChildrenComponent
  },
  setup () {
    const data = ref('aaa')
    return {
      data
    }
  }
};
</script>
...

//子组件
<template>
  <div>
    <el-input v-model="value"></el-input>
  </div>
</template>

<script>
export default {
  name: "ChildrenComponent",
  props: ['modelValue'], 
  emits: ['update:modelValue'], 
  computed: { 
    value: { 
       get() { return this.modelValue }, 
       set(value) { this.$emit('update:modelValue', value) } 
     } 
   }
};
</script>

这种方法有一个坑,你可以通过在set里面打印数据发现,当父子之间双向绑定的数据为基本数据类型时,能触发computed里的set方法,所以方法一的确是通过set触发emit来修改父组件内的data的。但是,当父子之间双向绑定的数据为引用类型数据的时候,比如对象和数组,这时候修改数据不会触发computed的set方法,因为在这里,computed监听的是引用类型数据的存储地址,而这个地址在数据修改时并不会改变,所以不会触发set方法,也就是不会调用this.$emit('update:modelValue', value)去通知父组件修改数据。但是你依然能看到,父组件数据的确被修改了,这是因为这时,在子组件中,<el-input v-model="value"></el-input>等价于<el-input v-model="props.modelValue"></el-input>,相当于你是自己在子组件修改了props的数据。

虽然这种方式依然能实现我们想要的效果,但是我个人并不推荐,用官方的解释就是,所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。

方法二 利用:modelValue和@update:modelValue

首先,对于在组件中使用v-model,实际上等价于同时使用:modelValue和@update:modelValue,因为v-model只是一个语法糖,在代码背后,模板编译器会对 v-model 进行更冗长的等价展开,所以我们可以这样写:

//父组件
<template>
  <div>
    <ChildrenComponent v-model="Obj"></ChildrenComponent>
  </div>
</template>

<script>
import { ref } from 'vue'
import ChildrenComponent from './ChildrenComponent.vue'
export default {
  name: "FatherComponent",
  components: {
    ChildrenComponent
  },
  setup () {
    const Obj = ref({data:''})
    return {
      Obj
    }
  }
};
</script>
...

//子组件
<template lang="ts">
  <div>
    <el-input :model-value="modelValue.data"
               @update:modelValue="handleValueChange(value,'data')">
    </el-input>
  </div>
</template>

<script>
export default {
  name: "ChildrenComponent",
  props: {
    modelValue: {
      type: Object,
    },
  }, 
  emits: ['update:modelValue'], 
  setup(props, { emit }){
    const handleValueChange = (value: any,field: string) => {
      emit("update:modelValue", { ...props.modelValue, [field]: value })
    }
  }
};
</script>

这种方式即使父子之间传递的是引用对象数据,也会触发emit去通知父组件数据更新,而不是在子组件内部直接修改props的数据

总结

如果父子之间双向绑定的是基本数据类型,那么两种方法都可以使用,如果父子之间双向绑定的是引用数据类型,那么第二种方法我觉得要合适一些。