Vue 3 组件 v-model 完全指南(四):参数与动态绑定完全指南——动态参数语法与条件绑定实战

扫描 二维码 关注或者微信搜一搜:编程智域 前端至全栈交流与成长

发现 1000+ 提升效率与开发的 AI 工具和实用程序https://tools.cmdragon.cn/

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>当前绑定键:{{ modelKey }}</p>
     <p>当前值:{{ modelValue }}</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 }"
     >
       {{ 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>{{ field.label }}</label>
     <component
       :is="getComponent(field.type)"
       v-model:[field.key]="formData[field.key]"
     />
   </div>

   <pre>{{ formData }}</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 }"
       >
         {{ getModeLabel(m) }}
       </button>
     </div>
   </div>

   <div class="value-display">
     <h4>当前值:</h4>
     <div v-if="mode === 'text'">文本:{{ textValue }}</div>
     <div v-else-if="mode === 'number'">数字:{{ numberValue }}</div>
     <div v-else-if="mode === 'select'">选项:{{ selectValue }}</div>
     <div v-else>日期:{{ dateValue }}</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>共享值:{{ sharedValue }}</h4>
     <p>当前组件:{{ currentComponent?.name || "未知" }}</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>{{ 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>{{ formData }}</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-ifv-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

余下文章内容请点击跳转至 个人博客页面 或者 扫描 二维码 关注或者微信搜一搜:编程智域 前端至全栈交流与成长,阅读完整的文章:Vue 3 组件 v-model 第四章:参数与动态绑定完全指南

往期文章归档

</details>

免费好用的热门在线工具

</details>

评论

此博客中的热门博文

数据库与编程语言的连接

深入探讨聚合函数(COUNT, SUM, AVG, MAX, MIN):分析和总结数据的新视野

数据库的创建与删除:理论与实践