Vue 双向绑定 v-model 完全指南

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 自动去空格,用户输入更整洁

使用提议

  1. 输入框用 .trim:避免首尾空格问题
  2. 数字输入用 .number:避免字符串计算错误
  3. 搜索框用 .lazy:减少不必要的更新
  4. 密码框不用修饰符:保持原样输入

记住:双向绑定让数据流动,修饰符让绑定更智能!

© 版权声明

相关文章

暂无评论

none
暂无评论...