目录概览
双向绑定(v-model 语法糖)
以下是最简单的Vue3 基于V-model 实现父子组件通信的实例
以下代码仍然建议直接在Vue3演练平台中执行,案例3在本地运行
案例1:使用默认modelValue 实现通信
- 在Vue中,v-model是一个语法糖,它背后其实依赖于props和emits 的约定。
- v-model 的工作原理是基于默认的
modelValue
和update: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 的默认行为是基于一个特定的属性和事件名称: modelValue
和 update: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>