【Vue.js】组件数据通信:基于Vue3 v-model 语法糖实现双向数据绑定

双向绑定(v-model 语法糖)

以下是最简单的Vue3 基于V-model 实现父子组件通信的实例
以下代码仍然建议直接在Vue3演练平台中执行,案例3在本地运行

案例1:使用默认modelValue 实现通信

  • 在Vue中,v-model是一个语法糖,它背后其实依赖于props和emits 的约定。
  • v-model 的工作原理是基于默认的 modelValueupdate:modelValue 。这是 Vue 的约定,而不是随意可以更改的。
  • v-model 的默认行为:父组件通过 v-model 绑定的值会被传递到子组件的 modelValue 属性,子组件通过触发 update:modelValue 事件来更新父组件的值。

在这里插入图片描述
在下面的案例中,子组件通过v-model 将输入框绑定,输入框输入任何值都会实时在父组件中接收

index.vue (父组件)

<template>
  <div>
    <h1>父组件</h1>
    <p>父组件中的值:{{ parentValue }}</p>
    <ChildComponent v-model="parentValue" />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

// 定义父组件的值
const parentValue = ref('');
</script>

【父组件过程】

  • 父组件中先注册子组件,并在子组件标签中,绑定v-model。
  • 父组件通过 v-model 将 parentValue 传递给子组件。父组件中绑定到 v-model 的变量名可以是任意的,例如 parentValue 、 dataValue 或其他任何名字。重要的是这个变量是一个响应式引用。
  • 注意,尽管父组件定义的响应式变量是任意名称,但是如果父组件直接写成 v-model="任意变量名" 时候,子组件就必须使用响应式变量 modelValue 接收,不可改变!

父组件v-model 完整展开形式是:
<ChildComponent :modelValue="parentValue" @update:modelValue="parentValue = $event"/>

ChildComponent.vue(子组件)

<template>
  <div>
    <h2>子组件</h2>
    <p>子组件接收到的值:{{ modelValue }}</p>
    <input
      type="text"
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    />
  </div>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';

// 定义 props,接收来自父组件的值
const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  }
});
// 定义 emits,用于将值更新事件传递回父组件
const emit = defineEmits(['update:modelValue']);
</script>

子组件中定义的 props 名称(如 modelValue )并不需要与父组件的变量名一致。它只是子组件内部用来接收父组件传递值的属性名。

【子组件过程】

  • 子组件使用 defineProps 接收来自父组件的 modelValue 。
  • 使用 defineEmits 定义可以发出的事件( update:modelValue )
  • 通过 $emit 将输入框的值更新事件传递回父组
  • 当输入框的值发生变化时,通过 $emit('update:modelValue', newValue) 将新的值传递回父组件。

【分析】

  • 父组件的 parentValue 和自组建的输入框值,通过v-model实现了双向绑定。
  • 在子组件中修改输入框的值,会自动更新父组件的parentValue。反之亦然。

【注意】:在 Vue 3 中,v-model 的默认行为是基于一个特定的属性和事件名称: modelValueupdate:modelValue 。当你使用 v-model 时,Vue 会自动将父组件的值绑定到子组件的 modelValue 属性,并监听子组件发出的 update:modelValue 事件来更新父组件的值。如果你将 modelValue 更改为 modelVal ,Vue 的默认行为将不再适用,除非你明确地告诉 Vue 你正在使用一个自定义的 v-model 属性。

默认modelValue 实现父子绑定的实例2

以下代码不能运行,仅供参考。

<!-- 父组件 Parent.vue -->
<Child v-model="inputValue" />
<!-- 等价于 -->
<Child :modelValue="inputValue" @update:modelValue="newVal => inputValue = newVal" />
<!-- 子组件 Child.vue -->
<script setup>
const props = defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);

const handleChange = (e) => {
  emit('update:modelValue', e.target.value); // 触发更新
};
</script>
<template>
  <input :value="modelValue" @input="handleChange" />
</template>

关键点:

  • 父组件 v-model='任意响应式变量值' 默认绑定子组件的 modelValue 属性和 update:modelValue 事件
  • 可自定义名称:v-model:title="pageTitle"

案例2:自定义名 v-model 实现父子通信

在这里插入图片描述

如果子组件接收父组件传过来的值时,子组件不是使用名称为 modelValue 的响应式变量接收,而是用别的响应式变量接收,比如 modelVal,此时父子组件通信就不再遵循默认的modelValue了。此时,父组件使用v-model绑定的变量名,必须与子组件接收的变量名一致

index.vue(父组件)
子组件可以使用自定义的 prop 名称(如 Val ),但父组件就必须要显式指定 v-model:Val

<template>
  <div>
    <h1>父组件</h1>
    <p>父组件中的值:{{ parentValue }}</p>
    <Child v-model:Val="parentValue" />
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import index from "./Child.vue";

// 定义父组件的值
const parentValue = ref("123");
</script>

Child.vue(子组件)
这个实例中,子组件不再使用默认的响应式变量名 modelValue 接收父组件的值,而是使用 Val 来接收。

<template>
  <div>
    <h2>子组件</h2>
    <p>子组件接收到的值:{{ Val }}</p>
    <input
      type="text"
      :value="Val"
      @input="$emit('update:Val', $event.target.value)"
    />
  </div>
</template>

<script setup lang="ts">
import { defineProps, defineEmits } from "vue";

// 定义 props,接收来自父组件的值
const props = defineProps({
  Val: {
    type: String,
    default: ""
  }
});

// 定义 emits,用于将值更新事件传递回父组件
const emit = defineEmits(["update:Val"]);
</script>

案例3:父子通信—基于elment-plus的form表单提交与更新

这其实是一个常用的功能,初期开发vue项目,对于简单的功能,可以把编辑/新增的Dialog Form表单 写到同一个页面中。但是随着项目功能的日益复杂,有些功能就需要封装出来,实现功能分离,让父组件代码瘦身,同时让新增/编辑功能作为一个单独的模块,让父组件去引用,能够提升代码的逻辑清晰度。

以下代码由于引入了elment-plus,建议在本地搭建一个简单的vue项目去使用
在这里插入图片描述
【效果】
当我在父组件中引入子组件的Dialog后,父组件点击编辑 按钮后,会让子组件中的Dialog 显示。子组件编辑表单后,会把表单更新至父组件中。

以下是最简单的父子组件通信

  • 在父组件注册子组件Dialog.vue,父页面控制Dialog是否显示对话框
  • 子组件对话框输入数据后,提交给父组件,父组件渲染更新。

index.vue(父组件)


<template>
  <div>
    <h1>退库单</h1>

    <!-- 显示当前数据 -->
    <div class="data-display">
      <p>物料名称: {{ formData.materialName || "暂无" }}</p>
      <p>退库数量: {{ formData.quantity }}</p>
      <p>退库原因: {{ formData.reason || "暂无" }}</p>
    </div>

    <!-- 打开弹窗的按钮 -->
    <el-button type="primary" @click="showDialog = true">
      编辑退库单
    </el-button>

    <!-- 子组件弹窗 -->
    <ReturnDialog
      v-model="showDialog"
      :form-data="formData"
      @submit="handleSubmit"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import ReturnDialog from "./ReturnDialog.vue";
// 控制弹窗显示
const showDialog = ref(false);

// 表单数据
const formData = ref({
  materialName: "默认物料",
  quantity: 10,
  reason: "质量不合格"
});

// 接收子组件提交的数据
const handleSubmit = newData => {
  formData.value = { ...newData };
  showDialog.value = false;
};
</script>

<style scoped>
.data-display {
  margin: 20px 0;
  padding: 15px;
  border: 1px solid #ebeef5;
  border-radius: 4px;
}
</style>

ReturnDialog.vue(子组件)


<template>
  <el-dialog v-model="dialogVisible" title="编辑退库单" width="50%">
    <el-form :model="localFormData">
      <el-form-item label="物料名称">
        <el-input v-model="localFormData.materialName" />
      </el-form-item>
      <el-form-item label="退库数量">
        <el-input-number v-model="localFormData.quantity" :min="1" />
      </el-form-item>
      <el-form-item label="退库原因">
        <el-input v-model="localFormData.reason" type="textarea" />
      </el-form-item>
    </el-form>

    <template #footer>
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button type="primary" @click="handleSubmit">提交</el-button>
    </template>
  </el-dialog>
</template>

<script setup lang="ts">
import { ref, watch, computed } from "vue";

const props = defineProps({
  modelValue: Boolean, // v-model 绑定的显示状态
  formData: Object // 父组件传递的表单数据
});

const emit = defineEmits([
  "update:modelValue", // 必须:用于更新 v-model
  "submit" // 自定义事件:提交数据
]);

// 控制弹窗显示
const dialogVisible = computed({
  get: () => props.modelValue,
  set: val => emit("update:modelValue", val)
});

// 本地表单数据(避免直接修改props)
const localFormData = ref({ ...props.formData });

// 当父组件数据变化时更新本地数据
watch(
  () => props.formData,
  newVal => {
    localFormData.value = { ...newVal };
  },
  { deep: true }
);

// 提交表单
const handleSubmit = () => {
  emit("submit", { ...localFormData.value });
};
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Issac-Clarke

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值