表单修饰符

Vue 为 v-model 提供了三个常用修饰符,用于处理常见的表单输入场景。这些修饰符能简化代码,提升用户体验。

.lazy 修饰符

默认行为

默认情况下,v-modelinput 事件中同步输入框的值,即每次按键都会触发更新:

<input v-model="text">
<p>{{ text }}</p>

用户输入 "hello",每次按键都会触发更新:

  • 输入 "h" → 更新
  • 输入 "he" → 更新
  • 输入 "hel" → 更新
  • ...

使用 .lazy

添加 .lazy 修饰符后,改为在 change 事件后同步,即失去焦点或回车后才更新:

<input v-model.lazy="text">
<p>{{ text }}</p>

用户输入 "hello",只有失去焦点或按回车才会更新。

适用场景

1. 表单验证

不需要实时验证的场景,避免频繁触发验证:

<form>
  <input 
    v-model.lazy="email" 
    @change="validateEmail"
    placeholder="邮箱"
  >
  <span v-if="error">{{ error }}</span>
</form>

<script>
new Vue({
  data: {
    email: '',
    error: ''
  },
  methods: {
    validateEmail: function() {
      const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
      this.error = re.test(this.email) ? '' : '邮箱格式不正确'
    }
  }
})
</script>

2. 中文输入法

使用中文输入法时,拼音输入过程中也会触发更新。使用 .lazy 可以等输入完成再更新:

<!-- 拼音输入过程中不会触发更新 -->
<input v-model.lazy="keyword" placeholder="搜索">

3. 性能优化

对于需要频繁计算或请求的场景,减少触发次数:

<!-- 搜索建议:失去焦点后才请求 -->
<input v-model.lazy="keyword" @change="fetchSuggestions">

.number 修饰符

默认行为

即使使用 type="number"v-model 返回的仍然是字符串:

<input type="number" v-model="age">
<p>类型:{{ typeof age }}</p>
<!-- 输入 18,显示 "string" -->

使用 .number

添加 .number 修饰符后,自动将输入转换为数字:

<input type="number" v-model.number="age">
<p>类型:{{ typeof age }}</p>
<!-- 输入 18,显示 "number" -->

转换规则

  • 如果输入值能被 parseFloat() 解析,返回数字
  • 如果无法解析,返回原始字符串
<input v-model.number="value">

<!-- 输入 "123" → 123 (数字) -->
<!-- 输入 "123.45" → 123.45 (数字) -->
<!-- 输入 "abc" → "abc" (字符串) -->
<!-- 输入 "123abc" → 123 (数字,parseFloat 的行为) -->

适用场景

1. 年龄、数量等数字输入

<input type="number" v-model.number="age" placeholder="年龄">
<input type="number" v-model.number="quantity" placeholder="数量">

2. 避免字符串比较问题

<!-- ❌ 字符串比较 -->
<input type="number" v-model="age">
<p v-if="age >= 18">成年人</p>
<!-- "18" >= 18 → false(字符串和数字比较) -->

<!-- ✅ 数字比较 -->
<input type="number" v-model.number="age">
<p v-if="age >= 18">成年人</p>
<!-- 18 >= 18 → true -->

3. 计算场景

<div>
  <input type="number" v-model.number="price" placeholder="单价">
  <input type="number" v-model.number="quantity" placeholder="数量">
  <p>总价:{{ price * quantity }}</p>
</div>

<!-- 如果不加 .number,会变成字符串拼接 -->
<!-- "10" * "2" = 20 (正确) -->
<!-- 但 "10" + "2" = "102" (错误) -->

注意事项

空值处理

输入为空时,.number 返回空字符串,而不是 0:

<input v-model.number="value">
<!-- 空输入 → "" (空字符串) -->
<!-- 需要手动处理 -->

如果需要默认为 0,可以使用计算属性:

computed: {
  normalizedValue: {
    get: function() {
      return this.value
    },
    set: function(val) {
      this.value = val === '' ? 0 : val
    }
  }
}

.trim 修饰符

默认行为

用户输入时可能不小心在首尾输入空格:

<input v-model="text">
<!-- 输入 "  hello  " → text = "  hello  " -->

使用 .trim

添加 .trim 修饰符后,自动去除首尾空格:

<input v-model.trim="text">
<!-- 输入 "  hello  " → text = "hello" -->

适用场景

1. 用户名、搜索关键词

<input v-model.trim="username" placeholder="用户名">
<input v-model.trim="keyword" placeholder="搜索">

2. 避免空格导致的验证问题

<input v-model.trim="email" placeholder="邮箱">
<!-- 用户不小心输入空格,自动去除 -->

3. 表单提交前处理

<form @submit.prevent="submit">
  <input v-model.trim="name" placeholder="姓名">
  <input v-model.trim="email" placeholder="邮箱">
  <button type="submit">提交</button>
</form>

注意事项

中间空格保留

.trim 只去除首尾空格,中间的空格会保留:

<input v-model.trim="text">
<!-- 输入 "  hello  world  " → "hello  world" -->

修饰符组合

修饰符可以组合使用,执行顺序从左到右:

<!-- lazy + trim -->
<input v-model.lazy.trim="text">
<!-- 失去焦点后更新,并去除首尾空格 -->

<!-- number + trim -->
<input v-model.number.trim="price">
<!-- 转换为数字,并去除首尾空格 -->

<!-- lazy + number + trim -->
<input v-model.lazy.number.trim="age">
<!-- 失去焦点后更新,转换为数字,去除首尾空格 -->

实战示例

用户注册表单

<div id="app">
  <form @submit.prevent="register">
    <div class="form-group">
      <label>用户名</label>
      <input 
        v-model.trim="form.username"
        placeholder="用户名(自动去除空格)"
      >
    </div>
    
    <div class="form-group">
      <label>邮箱</label>
      <input 
        type="email"
        v-model.trim.lazy="form.email"
        @change="validateEmail"
        placeholder="邮箱"
      >
      <span v-if="errors.email" class="error">{{ errors.email }}</span>
    </div>
    
    <div class="form-group">
      <label>年龄</label>
      <input 
        type="number"
        v-model.number="form.age"
        placeholder="年龄"
      >
    </div>
    
    <div class="form-group">
      <label>手机号</label>
      <input 
        v-model.trim="form.phone"
        placeholder="手机号"
      >
    </div>
    
    <button type="submit">注册</button>
  </form>
  
  <pre>{{ form }}</pre>
</div>

<script>
new Vue({
  el: '#app',
  data: {
    form: {
      username: '',
      email: '',
      age: null,
      phone: ''
    },
    errors: {
      email: ''
    }
  },
  methods: {
    validateEmail: function() {
      const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
      this.errors.email = re.test(this.form.email) ? '' : '邮箱格式不正确'
    },
    register: function() {
      console.log('注册数据:', this.form)
      // 发送到服务器...
    }
  }
})
</script>

价格计算器

<div id="app">
  <div class="calculator">
    <div class="row">
      <label>单价</label>
      <input 
        type="number" 
        v-model.number.trim="price"
        placeholder="单价"
      >
    </div>
    
    <div class="row">
      <label>数量</label>
      <input 
        type="number" 
        v-model.number.trim="quantity"
        placeholder="数量"
      >
    </div>
    
    <div class="row">
      <label>折扣</label>
      <input 
        type="number" 
        v-model.number.trim="discount"
        placeholder="折扣(%)"
      >
    </div>
    
    <div class="result">
      <p>小计:¥{{ subtotal.toFixed(2) }}</p>
      <p>折扣:-¥{{ discountAmount.toFixed(2) }}</p>
      <p class="total">总计:¥{{ total.toFixed(2) }}</p>
    </div>
  </div>
</div>

<script>
new Vue({
  el: '#app',
  data: {
    price: 0,
    quantity: 1,
    discount: 0
  },
  computed: {
    subtotal: function() {
      return this.price * this.quantity
    },
    discountAmount: function() {
      return this.subtotal * (this.discount / 100)
    },
    total: function() {
      return this.subtotal - this.discountAmount
    }
  }
})
</script>

搜索表单

<div id="app">
  <div class="search-form">
    <input 
      v-model.lazy.trim="keyword"
      @change="search"
      placeholder="输入关键词搜索(回车或失焦触发)"
    >
    <button @click="search">搜索</button>
  </div>
  
  <div v-if="loading" class="loading">搜索中...</div>
  
  <div v-if="results.length > 0" class="results">
    <div v-for="item in results" :key="item.id" class="result-item">
      <h3>{{ item.title }}</h3>
      <p>{{ item.description }}</p>
    </div>
  </div>
</div>

<script>
new Vue({
  el: '#app',
  data: {
    keyword: '',
    loading: false,
    results: []
  },
  methods: {
    search: function() {
      if (!this.keyword) {
        this.results = []
        return
      }
      
      this.loading = true
      
      // 模拟 API 调用
      setTimeout(() => {
        this.results = [
          { id: 1, title: this.keyword + ' 结果一', description: '描述...' },
          { id: 2, title: this.keyword + ' 结果二', description: '描述...' },
          { id: 3, title: this.keyword + ' 结果三', description: '描述...' }
        ]
        this.loading = false
      }, 500)
    }
  }
})
</script>

小结

表单修饰符使用场景总结:

修饰符作用适用场景
.lazy失去焦点后更新表单验证、中文输入法、性能优化
.number转换为数字年龄、数量、价格等数字输入
.trim去除首尾空格用户名、搜索关键词、邮箱

合理使用修饰符,能让表单处理更简洁、用户体验更好。记住:

  1. 不要滥用:修饰符虽好,但不是所有场景都需要
  2. 理解原理:知道修饰符做了什么,遇到问题才能排查
  3. 组合使用:多个修饰符可以组合,注意执行顺序

至此,Vue2 的表单绑定内容已经全部讲解完毕。