Vue 3 组件 v-model 完全指南(四):参数与动态绑定完全指南——动态参数语法与条件绑定实战
扫描 关注或者微信搜一搜:编程智域 前端至全栈交流与成长
1. 动态参数语法
1.1 基础动态参数
Vue 3 允许使用动态参数来实现灵活的 v-model 绑定:
<!-- DynamicParam.vue - 动态参数基础 -->
<template>
<div class="dynamic-param">
<component :is="inputType" v-model:[modelKey]="modelValue" />
<div class="controls">
<button @click="changeModel('text')">文本</button>
<button @click="changeModel('number')">数字</button>
<button @click="changeModel('email')">邮箱</button>
</div>
<div class="info">
<p>当前绑定键:</p>
<p>当前值:</p>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import TextInput from "./TextInput.vue";
import NumberInput from "./NumberInput.vue";
import EmailInput from "./EmailInput.vue";
const inputType = ref(TextInput);
const modelKey = ref("text");
const modelValue = ref("");
const components = {
text: TextInput,
number: NumberInput,
email: EmailInput,
};
const changeModel = (type) => {
modelKey.value = type;
modelValue.value = "";
inputType.value = components[type];
};
</script>
<style scoped>
.dynamic-param {
padding: 24px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.controls {
display: flex;
gap: 12px;
margin: 20px 0;
}
.controls button {
padding: 8px 16px;
background: #42b883;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.3s;
}
.controls button:hover {
background: #369970;
}
.info {
margin-top: 20px;
padding: 16px;
background: #f5f5f5;
border-radius: 6px;
}
.info p {
margin: 8px 0;
color: #666;
}
</style>1.2 动态参数表达式
<!-- DynamicExpression.vue - 动态参数表达式 -->
<template>
<div class="dynamic-expression">
<input-component v-model:[computedKey]="value" />
<select v-model="keyPrefix">
<option value="user">用户</option>
<option value="product">产品</option>
<option value="order">订单</option>
</select>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
const keyPrefix = ref("user");
const value = ref("");
const computedKey = computed(() => {
return `${keyPrefix.value}Name`;
});
</script>2. 运行时动态切换绑定属性
2.1 条件绑定
<!-- ConditionalBinding.vue - 条件绑定 -->
<template>
<div class="conditional-binding">
<component
:is="currentComponent"
v-bind="bindingProps"
@[updateEvent]="handleUpdate"
/>
<div class="switcher">
<button
v-for="type in types"
:key="type"
@click="switchType(type)"
:class="{ active: currentType === type }"
>
</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
import TextInput from "./TextInput.vue";
import NumberInput from "./NumberInput.vue";
import DateInput from "./DateInput.vue";
const currentType = ref("text");
const textValue = ref("");
const numberValue = ref(0);
const dateValue = ref("");
const types = ["text", "number", "date"];
const currentComponent = computed(() => {
const map = {
text: TextInput,
number: NumberInput,
date: DateInput,
};
return map[currentType.value];
});
const bindingProps = computed(() => {
const map = {
text: { modelValue: textValue.value, placeholder: "文本" },
number: { modelValue: numberValue.value, min: 0 },
date: { modelValue: dateValue.value, format: "YYYY-MM-DD" },
};
return map[currentType.value];
});
const updateEvent = "update:modelValue";
const handleUpdate = (newValue) => {
if (currentType.value === "text") {
textValue.value = newValue;
} else if (currentType.value === "number") {
numberValue.value = newValue;
} else {
dateValue.value = newValue;
}
};
const switchType = (type) => {
currentType.value = type;
};
</script>
<style scoped>
.conditional-binding {
padding: 24px;
background: #fff;
border-radius: 8px;
}
.switcher {
display: flex;
gap: 8px;
margin-top: 16px;
}
.switcher button {
padding: 8px 16px;
border: 2px solid #ddd;
background: #fff;
cursor: pointer;
border-radius: 6px;
transition: all 0.3s;
}
.switcher button.active {
border-color: #42b883;
background: #42b883;
color: white;
}
</style>2.2 动态字段绑定
<!-- DynamicFieldBinding.vue - 动态字段绑定 -->
<template>
<div class="dynamic-field-binding">
<div v-for="field in fields" :key="field.key" class="field-group">
<label></label>
<component
:is="getComponent(field.type)"
v-model:[field.key]="formData[field.key]"
/>
</div>
<pre></pre>
</div>
</template>
<script setup>
import { reactive } from "vue";
import TextInput from "./TextInput.vue";
import NumberInput from "./NumberInput.vue";
import SelectInput from "./SelectInput.vue";
const fields = [
{ key: "username", label: "用户名", type: "text" },
{ key: "age", label: "年龄", type: "number" },
{ key: "role", label: "角色", type: "select" },
];
const formData = reactive({
username: "",
age: 18,
role: "user",
});
const components = {
text: TextInput,
number: NumberInput,
select: SelectInput,
};
const getComponent = (type) => {
return components[type] || TextInput;
};
</script>
<style scoped>
.dynamic-field-binding {
padding: 24px;
background: #fff;
border-radius: 8px;
}
.field-group {
margin-bottom: 20px;
}
.field-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
pre {
margin-top: 24px;
padding: 16px;
background: #282c34;
color: #abb2bf;
border-radius: 6px;
overflow-x: auto;
}
</style>3. 条件 v-model 的实现
3.1 v-if 与 v-model 结合
<!-- ConditionalVModel.vue - 条件 v-model -->
<template>
<div class="conditional-v-model">
<div v-if="mode === 'text'">
<TextInput v-model="textValue" placeholder="请输入文本" />
</div>
<div v-else-if="mode === 'number'">
<NumberInput v-model="numberValue" :min="0" :max="100" />
</div>
<div v-else-if="mode === 'select'">
<SelectInput v-model="selectValue" :options="options" />
</div>
<div v-else>
<DateInput v-model="dateValue" format="YYYY-MM-DD" />
</div>
<div class="mode-selector">
<h4>选择模式:</h4>
<div class="buttons">
<button
v-for="m in modes"
:key="m"
@click="mode = m"
:class="{ active: mode === m }"
>
</button>
</div>
</div>
<div class="value-display">
<h4>当前值:</h4>
<div v-if="mode === 'text'">文本:</div>
<div v-else-if="mode === 'number'">数字:</div>
<div v-else-if="mode === 'select'">选项:</div>
<div v-else>日期:</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import TextInput from "./TextInput.vue";
import NumberInput from "./NumberInput.vue";
import SelectInput from "./SelectInput.vue";
import DateInput from "./DateInput.vue";
const mode = ref("text");
const textValue = ref("");
const numberValue = ref(0);
const selectValue = ref("");
const dateValue = ref("");
const modes = ["text", "number", "select", "date"];
const options = [
{ label: "选项 1", value: "option1" },
{ label: "选项 2", value: "option2" },
{ label: "选项 3", value: "option3" },
];
const getModeLabel = (m) => {
const labels = {
text: "文本",
number: "数字",
select: "选择",
date: "日期",
};
return labels[m];
};
</script>
<style scoped>
.conditional-v-model {
padding: 24px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.mode-selector {
margin-top: 24px;
}
.mode-selector h4 {
margin-top: 0;
margin-bottom: 12px;
color: #666;
}
.buttons {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.buttons button {
padding: 8px 16px;
border: 2px solid #ddd;
background: #fff;
cursor: pointer;
border-radius: 6px;
transition: all 0.3s;
}
.buttons button.active {
border-color: #42b883;
background: #42b883;
color: white;
}
.value-display {
margin-top: 24px;
padding: 16px;
background: #f5f5f5;
border-radius: 6px;
}
.value-display h4 {
margin-top: 0;
margin-bottom: 12px;
color: #666;
}
</style>3.2 动态组件与 v-model
<!-- DynamicComponentVModel.vue - 动态组件 v-model -->
<template>
<div class="dynamic-component-vmodel">
<component
:is="currentComponent"
v-model="sharedValue"
v-bind="componentProps"
/>
<div class="component-switcher">
<button @click="switchComponent('text')">文本输入</button>
<button @click="switchComponent('number')">数字输入</button>
<button @click="switchComponent('range')">范围输入</button>
</div>
<div class="shared-value">
<h4>共享值:</h4>
<p>当前组件:</p>
</div>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
import TextInput from "./TextInput.vue";
import NumberInput from "./NumberInput.vue";
import RangeInput from "./RangeInput.vue";
const currentComponentType = ref("text");
const sharedValue = ref("");
const currentComponent = computed(() => {
const map = {
text: TextInput,
number: NumberInput,
range: RangeInput,
};
return map[currentComponentType.value];
});
const componentProps = computed(() => {
if (currentComponentType.value === "number") {
return { min: 0, max: 100 };
} else if (currentComponentType.value === "range") {
return { min: 0, max: 100, step: 5 };
}
return {};
});
const switchComponent = (type) => {
currentComponentType.value = type;
// 根据类型初始化值
if (type === "number" || type === "range") {
sharedValue.value = 0;
} else {
sharedValue.value = "";
}
};
</script>
<style scoped>
.dynamic-component-vmodel {
padding: 24px;
background: #fff;
border-radius: 8px;
}
.component-switcher {
display: flex;
gap: 12px;
margin: 20px 0;
}
.component-switcher button {
padding: 10px 20px;
background: #42b883;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.3s;
}
.component-switcher button:hover {
background: #369970;
}
.shared-value {
margin-top: 24px;
padding: 16px;
background: #f5f5f5;
border-radius: 6px;
}
.shared-value h4 {
margin-top: 0;
color: #333;
}
.shared-value p {
margin: 8px 0;
color: #666;
}
</style>4. 实战示例:动态表单生成器
<!-- DynamicFormGenerator.vue - 动态表单生成器 -->
<template>
<div class="dynamic-form-generator">
<h3>动态表单生成器</h3>
<div
v-for="(field, index) in formFields"
:key="field.id"
class="form-field"
>
<label></label>
<component
:is="getFieldComponent(field.type)"
v-model:[field.key]="formData[field.key]"
v-bind="getFieldProps(field)"
/>
<button @click="removeField(index)" class="remove-btn">删除</button>
</div>
<div class="add-field">
<button @click="addField('text')" class="add-btn">添加文本字段</button>
<button @click="addField('number')" class="add-btn">添加数字字段</button>
<button @click="addField('select')" class="add-btn">添加选择字段</button>
</div>
<div class="form-data">
<h4>表单数据:</h4>
<pre></pre>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from "vue";
import TextInput from "./TextInput.vue";
import NumberInput from "./NumberInput.vue";
import SelectInput from "./SelectInput.vue";
const formFields = ref([
{ id: 1, key: "name", label: "姓名", type: "text" },
{ id: 2, key: "age", label: "年龄", type: "number" },
]);
const formData = reactive({
name: "",
age: 18,
});
const components = {
text: TextInput,
number: NumberInput,
select: SelectInput,
};
const getFieldComponent = (type) => {
return components[type] || TextInput;
};
const getFieldProps = (field) => {
if (field.type === "select") {
return {
options: [
{ label: "选项 1", value: "opt1" },
{ label: "选项 2", value: "opt2" },
],
};
}
if (field.type === "number") {
return { min: 0, max: 150 };
}
return {};
};
const addField = (type) => {
const key = `field_${Date.now()}`;
const id = Date.now();
formFields.value.push({
id,
key,
label: `新字段 ${formFields.value.length + 1}`,
type,
});
formData[key] = type === "number" ? 0 : "";
};
const removeField = (index) => {
const field = formFields.value[index];
delete formData[field.key];
formFields.value.splice(index, 1);
};
</script>
<style scoped>
.dynamic-form-generator {
padding: 24px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h3 {
margin-top: 0;
margin-bottom: 24px;
color: #333;
}
.form-field {
position: relative;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}
.form-field label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.remove-btn {
position: absolute;
right: 0;
top: 32px;
padding: 6px 12px;
background: #f44336;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.remove-btn:hover {
background: #d32f2f;
}
.add-field {
display: flex;
gap: 12px;
margin: 24px 0;
}
.add-btn {
padding: 10px 20px;
background: #42b883;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.3s;
}
.add-btn:hover {
background: #369970;
}
.form-data {
margin-top: 24px;
padding: 16px;
background: #f5f5f5;
border-radius: 6px;
}
.form-data h4 {
margin-top: 0;
color: #666;
}
.form-data pre {
background: #282c34;
color: #abb2bf;
padding: 16px;
border-radius: 6px;
overflow-x: auto;
}
</style>5. 课后 Quiz
题目 1:动态参数语法
问题: 如何使用动态参数绑定 v-model?
答案:
使用 v-model:[dynamicKey]="value" 语法,其中 dynamicKey 是一个响应式变量或计算属性。
题目 2:条件 v-model
问题: 如何实现条件 v-model?
答案:
结合 v-if 和 v-model,根据不同的条件渲染不同的组件并绑定不同的值。
题目 3:动态组件切换
问题: 动态组件切换时如何保持 v-model 的值?
答案: 使用一个共享的响应式变量,所有动态组件都绑定到同一个 v-model 上。
6. 常见报错解决方案
报错 1:动态参数为 null
产生原因:
动态参数值为 null 或 undefined
解决办法:
<!-- ✅ 确保动态参数有值 -->
<script setup>
const modelKey = ref("text"); // 不要使用 null
</script>报错 2:组件不匹配
产生原因:
动态组件不支持当前的 v-model 类型
解决办法:
<!-- ✅ 确保组件支持对应的 modelValue -->
<script setup>
const components = {
text: TextInput, // 支持 string
number: NumberInput, // 支持 number
};
</script>参考链接:https://vuejs.org/guide/components/v-model.html
余下文章内容请点击跳转至 个人博客页面 或者 扫描 关注或者微信搜一搜:编程智域 前端至全栈交流与成长,阅读完整的文章:
往期文章归档
</details>
免费好用的热门在线工具
评论
发表评论