Vue 双向绑定 v-model 完全指南
一、什么是双向绑定?
简单理解
单向绑定:数据变 → 视图变(只能从数据到视图)
双向绑定:数据变 ↔ 视图变(可以相互影响)
<!-- 单向绑定 -->
<input :value="message"> <!-- 只能从data到input -->
<p>{{ message }}</p> <!-- 只能从data到p -->
<!-- 双向绑定 -->
<input v-model="message"> <!-- 可以相互影响 -->
<!-- 输入框修改 → 更新message -->
<!-- message修改 → 更新输入框 -->
二、基本用法
<template>
<div>
<!-- 1. 文本输入框 -->
<input v-model="text" placeholder="输入文字">
<p>输入的内容:{{ text }}</p>
<!-- 2. 多行文本 -->
<textarea v-model="message" placeholder="多行文本"></textarea>
<p>消息:{{ message }}</p>
<!-- 3. 复选框(单个) -->
<input type="checkbox" v-model="checked">
<label>是否同意协议</label>
<p>状态:{{ checked }}</p>
<!-- 4. 复选框(多个) -->
<div>
<input type="checkbox" value="vue" v-model="skills">
<label>Vue</label>
<input type="checkbox" value="react" v-model="skills">
<label>React</label>
<input type="checkbox" value="angular" v-model="skills">
<label>Angular</label>
</div>
<p>已选技能:{{ skills }}</p>
<!-- 5. 单选按钮 -->
<div>
<input type="radio" value="男" v-model="gender">
<label>男</label>
<input type="radio" value="女" v-model="gender">
<label>女</label>
</div>
<p>性别:{{ gender }}</p>
<!-- 6. 下拉选择 -->
<select v-model="selected">
<option value="">请选择</option>
<option value="A">选项A</option>
<option value="B">选项B</option>
</select>
<p>已选择:{{ selected }}</p>
</div>
</template>
<script>
export default {
data() {
return {
text: '',
message: '',
checked: false,
skills: [], // 多选框用数组
gender: '男', // 单选框用字符串
selected: '' // 下拉框用字符串
}
}
}
</script>
三、三个修饰符详解
1. .lazy- 懒更新
默认:输入时实时更新
用 .lazy:失去焦点时才更新
<template>
<div>
<!-- 对比示例 -->
<h4>普通 v-model(实时更新)</h4>
<input v-model="text1">
<p>实时显示:{{ text1 }}</p>
<h4>v-model.lazy(懒更新)</h4>
<input v-model.lazy="text2">
<p>失去焦点时显示:{{ text2 }}</p>
<!-- 实际应用场景 -->
<input
v-model.lazy="searchKeyword"
placeholder="输入关键词搜索(输完再搜)"
>
<button @click="search">搜索</button>
</div>
</template>
<script>
export default {
data() {
return {
text1: '',
text2: '',
searchKeyword: ''
}
},
methods: {
search() {
console.log('搜索关键词:', this.searchKeyword)
}
}
}
</script>
什么时候用:搜索框、表单验证等不需要实时更新的场景
2. .number- 转为数字
默认:输入的值是字符串
用 .number:尝试转为数字
<template>
<div>
<!-- 问题:字符串相加 -->
<input v-model="age1" type="number">
<button @click="calculate1">计算10年后年龄</button>
<p>结果:{{ result1 }}</p>
<!-- 输入20 → 结果是"2010"(字符串拼接) -->
<!-- 解决方案:使用 .number -->
<input v-model.number="age2" type="number">
<button @click="calculate2">计算10年后年龄</button>
<p>结果:{{ result2 }}</p>
<!-- 输入20 → 结果是30(数字相加) -->
<!-- 购物车计算示例 -->
<div class="cart">
<input v-model.number="price" placeholder="单价">
<input v-model.number="quantity" placeholder="数量">
<p>总价:{{ total }}</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
age1: '',
age2: '',
result1: '',
result2: '',
price: 0,
quantity: 0
}
},
computed: {
total() {
return this.price * this.quantity
}
},
methods: {
calculate1() {
this.result1 = this.age1 + 10 // 字符串拼接
},
calculate2() {
this.result2 = this.age2 + 10 // 数字相加
}
}
}
</script>
注意:如果输入的不是数字,会得到 NaN
<input v-model.number="value">
<!-- 输入"abc" → value是NaN -->
<!-- 输入"123" → value是123(数字) -->
3. .trim- 去除首尾空格
用 .trim:自动去除输入内容的首尾空格
<template>
<div>
<!-- 用户注册示例 -->
<h4>用户注册</h4>
<!-- 没有.trim的问题 -->
<div>
<input v-model="username1" placeholder="用户名(无.trim)">
<p>用户名长度:{{ username1.length }}</p>
<!-- 输入" 张三 " → 长度是5(包含空格) -->
</div>
<!-- 使用.trim解决 -->
<div>
<input v-model.trim="username2" placeholder="用户名(有.trim)">
<p>用户名长度:{{ username2.length }}</p>
<!-- 输入" 张三 " → 长度是2(去掉空格) -->
</div>
<!-- 登录表单示例 -->
<div class="login-form">
<input v-model.trim="email" placeholder="邮箱">
<input v-model.trim="password" type="password" placeholder="密码">
<button @click="login">登录</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
username1: '',
username2: '',
email: '',
password: ''
}
},
methods: {
login() {
// 自动去除空格,不需要手动处理
console.log('邮箱:', this.email)
console.log('密码:', this.password)
}
}
}
</script>
什么时候用:用户名、邮箱、密码等需要去除空格的输入框
四、修饰符组合使用
<template>
<div>
<!-- 可以同时使用多个修饰符 -->
<!-- 搜索框:懒更新 + 去除空格 -->
<input
v-model.lazy.trim="searchKeyword"
placeholder="输入搜索关键词"
>
<!-- 价格输入:转为数字 + 去除空格 -->
<input
v-model.number.trim="price"
type="number"
placeholder="输入价格"
>
<!-- 组合顺序不影响结果 -->
<input v-model.lazy.number="age">
<input v-model.number.lazy="age"> <!-- 效果一样 -->
</div>
</template>
<script>
export default {
data() {
return {
searchKeyword: '',
price: 0,
age: 0
}
}
}
</script>
五、实际应用场景
场景1:用户注册表单
<template>
<div class="register-form">
<h3>用户注册</h3>
<!-- 用户名:去除空格 -->
<div class="form-group">
<label>用户名:</label>
<input v-model.trim="form.username" placeholder="2-10个字符">
<p v-if="form.username.length < 2" class="error">用户名太短</p>
</div>
<!-- 年龄:转为数字 -->
<div class="form-group">
<label>年龄:</label>
<input v-model.number="form.age" type="number" min="0" max="150">
<p v-if="form.age < 0 || form.age > 150" class="error">年龄无效</p>
</div>
<!-- 邮箱:去除空格 -->
<div class="form-group">
<label>邮箱:</label>
<input v-model.trim="form.email" type="email">
</div>
<!-- 个人简介:懒更新(不需要实时验证) -->
<div class="form-group">
<label>个人简介:</label>
<textarea v-model.lazy="form.bio" placeholder="介绍一下自己"></textarea>
</div>
<!-- 爱好:多选框 -->
<div class="form-group">
<label>爱好:</label>
<div>
<label><input type="checkbox" value="读书" v-model="form.hobbies"> 读书</label>
<label><input type="checkbox" value="运动" v-model="form.hobbies"> 运动</label>
<label><input type="checkbox" value="音乐" v-model="form.hobbies"> 音乐</label>
</div>
</div>
<!-- 性别:单选按钮 -->
<div class="form-group">
<label>性别:</label>
<div>
<label><input type="radio" value="male" v-model="form.gender"> 男</label>
<label><input type="radio" value="female" v-model="form.gender"> 女</label>
</div>
</div>
<button @click="submit">提交</button>
<!-- 预览表单数据 -->
<div class="preview">
<h4>表单数据预览:</h4>
<pre>{{ JSON.stringify(form, null, 2) }}</pre>
</div>
</div>
</template>
<script>
export default {
data() {
return {
form: {
username: '',
age: 0,
email: '',
bio: '',
hobbies: [],
gender: 'male'
}
}
},
methods: {
submit() {
console.log('提交表单:', this.form)
alert('注册成功!')
}
}
}
</script>
<style>
.form-group {
margin: 15px 0;
}
.form-group label {
display: inline-block;
width: 100px;
}
.error {
color: red;
font-size: 12px;
margin: 5px 0 0 100px;
}
.preview {
margin-top: 20px;
padding: 10px;
background-color: #f5f5f5;
border-radius: 4px;
}
</style>
场景2:商品筛选器
<template>
<div class="product-filter">
<h3>商品筛选</h3>
<!-- 关键词搜索:懒更新 + 去除空格 -->
<div class="filter-item">
<label>关键词:</label>
<input
v-model.lazy.trim="filters.keyword"
placeholder="输入商品名称"
>
</div>
<!-- 价格范围:转为数字 -->
<div class="filter-item">
<label>价格范围:</label>
<input v-model.number="filters.minPrice" placeholder="最低价">
<span> - </span>
<input v-model.number="filters.maxPrice" placeholder="最高价">
</div>
<!-- 商品分类:多选框 -->
<div class="filter-item">
<label>分类:</label>
<label><input type="checkbox" value="electronics" v-model="filters.categories"> 电子产品</label>
<label><input type="checkbox" value="clothing" v-model="filters.categories"> 服装</label>
<label><input type="checkbox" value="books" v-model="filters.categories"> 图书</label>
</div>
<!-- 排序方式:单选按钮 -->
<div class="filter-item">
<label>排序:</label>
<label><input type="radio" value="price_asc" v-model="filters.sort"> 价格从低到高</label>
<label><input type="radio" value="price_desc" v-model="filters.sort"> 价格从高到低</label>
<label><input type="radio" value="newest" v-model="filters.sort"> 最新上架</label>
</div>
<button @click="applyFilters">应用筛选</button>
<!-- 显示筛选结果 -->
<div class="results">
<h4>筛选条件:</h4>
<pre>{{ filters }}</pre>
</div>
</div>
</template>
<script>
export default {
data() {
return {
filters: {
keyword: '',
minPrice: 0,
maxPrice: 10000,
categories: [],
sort: 'newest'
}
}
},
methods: {
applyFilters() {
console.log('应用筛选条件:', this.filters)
// 这里可以调用API获取筛选后的商品
}
}
}
</script>
<style>
.filter-item {
margin: 10px 0;
}
.filter-item label {
display: inline-block;
width: 100px;
}
.results {
margin-top: 20px;
padding: 10px;
background-color: #f0f0f0;
border-radius: 4px;
}
</style>
场景3:实时计算器
<template>
<div class="calculator">
<h3>实时计算器</h3>
<!-- 数字输入:转为数字 -->
<div class="calc-inputs">
<input v-model.number="num1" placeholder="数字1">
<select v-model="operator">
<option value="+">+</option>
<option value="-">-</option>
<option value="*">×</option>
<option value="/">÷</option>
</select>
<input v-model.number="num2" placeholder="数字2">
<span>= {{ result }}</span>
</div>
<!-- 计算历史:懒更新 -->
<div class="calc-history">
<h4>计算历史</h4>
<input
v-model.lazy.trim="historyNote"
placeholder="添加备注(按回车或失去焦点保存)"
@keyup.enter="addHistory"
>
<button @click="addHistory">添加备注</button>
<ul>
<li v-for="(item, index) in history" :key="index">
{{ item }}
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
data() {
return {
num1: 0,
num2: 0,
operator: '+',
historyNote: '',
history: []
}
},
computed: {
result() {
switch (this.operator) {
case '+': return this.num1 + this.num2
case '-': return this.num1 - this.num2
case '*': return this.num1 * this.num2
case '/': return this.num2 !== 0 ? this.num1 / this.num2 : '无穷大'
default: return 0
}
}
},
methods: {
addHistory() {
if (this.historyNote.trim()) {
this.history.push(`${this.historyNote} (结果: ${this.result})`)
this.historyNote = ''
}
}
}
}
</script>
<style>
.calculator {
max-width: 400px;
margin: 0 auto;
}
.calc-inputs {
display: flex;
align-items: center;
gap: 10px;
margin: 20px 0;
}
.calc-inputs input {
width: 80px;
text-align: center;
padding: 8px;
}
.calc-history {
margin-top: 20px;
padding: 15px;
background-color: #f9f9f9;
border-radius: 8px;
}
.calc-history input {
width: 200px;
padding: 5px;
margin-right: 10px;
}
.calc-history ul {
list-style: none;
padding: 0;
margin-top: 10px;
}
.calc-history li {
padding: 5px 0;
border-bottom: 1px solid #eee;
}
</style>
六、注意事项
1. .number的边界情况
<template>
<div>
<input v-model.number="value">
<p>值:{{ value }},类型:{{ typeof value }}</p>
<!-- 输入"123" → 123 (number) -->
<!-- 输入"123abc" → NaN (number) -->
<!-- 输入"" → '' (string) -->
</div>
</template>
2. 不能一起用的修饰符
<!-- 正确:可以组合使用 -->
<input v-model.lazy.number.trim="value">
<!-- Vue 3 中,.lazy 只影响 input/change 事件 -->
<!-- 不影响 .number 和 .trim 的功能 -->
3. 与自定义组件的 v-model
<!-- 父组件 -->
<template>
<CustomInput v-model="message" />
<!-- 等同于 -->
<CustomInput
:modelValue="message"
@update:modelValue="message = $event"
/>
</template>
<!-- 子组件 CustomInput.vue -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
</template>
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue']
}
</script>
七、总结表格
|
修饰符 |
作用 |
示例 |
使用场景 |
|
v-model |
双向绑定 |
v-model=”text” |
所有表单元素 |
|
.lazy |
懒更新 |
v-model.lazy=”text” |
搜索框、表单验证 |
|
.number |
转为数字 |
v-model.number=”age” |
年龄、价格、数量 |
|
.trim |
去除首尾空格 |
v-model.trim=”username” |
用户名、邮箱、密码 |
记忆口诀
v-model 双向绑,表单数据不用忙
.lazy 更新不着急,失去焦点再处理
.number 字符串转数字,数学计算不出事
.trim 自动去空格,用户输入更整洁
使用提议
- 输入框用 .trim:避免首尾空格问题
- 数字输入用 .number:避免字符串计算错误
- 搜索框用 .lazy:减少不必要的更新
- 密码框不用修饰符:保持原样输入
记住:双向绑定让数据流动,修饰符让绑定更智能! ✨
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...





