Skip to content
On this page

表单输入绑定

v-model 基础用法

Vue.js 提供了 v-model 指令,用于在表单元素上创建双向数据绑定。它会根据控件类型自动选择正确的方法来更新元素。

文本输入

vue
<template>
  <div>
    <p>Message is: {{ message }}</p>
    <input v-model="message" placeholder="edit me">
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: ''
    }
  }
}
</script>

多行文本

vue
<template>
  <div>
    <p>Multiline message is:</p>
    <p style="white-space: pre-line;">{{ message }}</p>
    <textarea v-model="message" placeholder="add multiple lines"></textarea>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: ''
    }
  }
}
</script>

复选框

单个复选框,绑定到布尔值:

vue
<template>
  <div>
    <input type="checkbox" id="checkbox" v-model="checked">
    <label for="checkbox">{{ checked }}</label>
  </div>
</template>

<script>
export default {
  data() {
    return {
      checked: false
    }
  }
}
</script>
</template>

多个复选框,绑定到同一个数组:

vue
<template>
  <div>
    <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
    <label for="jack">Jack</label>
    <input type="checkbox" id="john" value="John" v-model="checkedNames">
    <label for="john">John</label>
    <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
    <label for="mike">Mike</label>
    <br>
    <span>Checked names: {{ checkedNames }}</span>
  </div>
</template>

<script>
export default {
  data() {
    return {
      checkedNames: []
    }
  }
}
</script>
</template>

单选按钮

vue
<template>
  <div>
    <input type="radio" id="one" value="One" v-model="picked">
    <label for="one">One</label>
    <input type="radio" id="two" value="Two" v-model="picked">
    <label for="two">Two</label>
    <br>
    <span>Picked: {{ picked }}</span>
  </div>
</template>

<script>
export default {
  data() {
    return {
      picked: ''
    }
  }
}
</script>
</template>

选择框

单选:

vue
<template>
  <div>
    <select v-model="selected">
      <option disabled value="">Please select one</option>
      <option>A</option>
      <option>B</option>
      <option>C</option>
    </select>
    <span>Selected: {{ selected }}</span>
  </div>
</template>

<script>
export default {
  data() {
    return {
      selected: ''
    }
  }
}
</script>
</template>

多选(绑定到一个数组):

vue
<template>
  <div>
    <select v-model="selected" multiple>
      <option>A</option>
      <option>B</option>
      <option>C</option>
    </select>
    <br>
    <span>Selected: {{ selected }}</span>
  </div>
</template>

<script>
export default {
  data() {
    return {
      selected: []
    }
  }
}
</script>
</template>

值绑定

对于单选按钮、复选框和选择框选项,v-model 绑定的值通常是静态字符串(对于复选框是布尔值):

vue
<!-- 当选中时,`picked` 为字符串 "a" -->
<input type="radio" v-model="picked" value="a">

但有时我们可能想将值绑定到 Vue 实例上的动态属性,这时可以使用 v-bind 来实现:

vue
<template>
  <div>
    <!-- 复选框 -->
    <input
      type="checkbox"
      v-model="toggle"
      :true-value="trueValue"
      :false-value="falseValue"
    >
    <p>Toggle value: {{ toggle }}</p>
    
    <!-- 单选按钮 -->
    <input type="radio" v-model="pick" :value="aValue">
    <input type="radio" v-model="pick" :value="bValue">
    <p>Selected value: {{ pick }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      toggle: false,
      trueValue: 'yes',
      falseValue: 'no',
      pick: null,
      aValue: 'a',
      bValue: 'b'
    }
  }
}
</script>
</template>

修饰符

v-model 指令提供了几个修饰符来处理常见的输入场景:

.lazy 修饰符

在默认情况下,v-model 在每次 input 事件后同步更新数据(除了 IME 组合期间)。你可以添加 lazy 修饰符,使 v-modelchange 事件后同步:

vue
<template>
  <div>
    <p>Without lazy: {{ message1 }}</p>
    <input v-model="message1">
    
    <p>With lazy: {{ message2 }}</p>
    <input v-model.lazy="message2">
  </div>
</template>

<script>
export default {
  data() {
    return {
      message1: '',
      message2: ''
    }
  }
}
</script>
</template>

.number 修饰符

如果想自动将用户的输入值转为数字类型,可以给 v-model 添加 number 修饰符:

vue
<template>
  <div>
    <input v-model="age" type="number" placeholder="Input age">
    <p>Age: {{ age }} (Type: {{ typeof age }})</p>
    
    <input v-model.number="age2" type="number" placeholder="Input age with .number">
    <p>Age2: {{ age2 }} (Type: {{ typeof age2 }})</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      age: '',
      age2: ''
    }
  }
}
</script>
</template>

.trim 修饰符

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

vue
<template>
  <div>
    <input v-model="msg1" placeholder="Without trim">
    <p>Message without trim: "{{ msg1 }}"</p>
    
    <input v-model.trim="msg2" placeholder="With trim">
    <p>Message with trim: "{{ msg2 }}"</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg1: '',
      msg2: ''
    }
  }
}
</script>
</template>

在组件上使用 v-model

自定义组件上的 v-model 默认情况下会利用名为 modelValue 的 prop 和名为 update:modelValue 的事件。

Vue 3 中的 v-model

vue
<!-- 子组件 ChildComponent.vue -->
<template>
  <input 
    :value="modelValue" 
    @input="$emit('update:modelValue', $event.target.value)"
  >
</template>

<script>
export default {
  name: 'ChildComponent',
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>
vue
<!-- 父组件使用 -->
<template>
  <div>
    <p>Parent message: {{ message }}</p>
    <ChildComponent v-model="message" />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue'

export default {
  components: {
    ChildComponent
  },
  data() {
    return {
      message: ''
    }
  }
}
</script>
</template>

使用 v-model 的参数

Vue 3 允许组件的 v-model 使用不同的 prop 和事件:

vue
<!-- ChildComponentWithArgs.vue -->
<template>
  <div>
    <input 
      :value="title" 
      @input="$emit('update:title', $event.target.value)"
      placeholder="Title"
    >
    <input 
      :value="content" 
      @input="$emit('update:content', $event.target.value)"
      placeholder="Content"
    >
  </div>
</template>

<script>
export default {
  props: ['title', 'content'],
  emits: ['update:title', 'update:content']
}
</script>
vue
<!-- 父组件 -->
<template>
  <div>
    <p>Title: {{ title }}</p>
    <p>Content: {{ content }}</p>
    <ChildComponentWithArgs 
      v-model:title="title" 
      v-model:content="content" 
    />
  </div>
</template>

<script>
import ChildComponentWithArgs from './ChildComponentWithArgs.vue'

export default {
  components: {
    ChildComponentWithArgs
  },
  data() {
    return {
      title: '',
      content: ''
    }
  }
}
</script>
</template>

组合式API中的表单处理

在组合式API中,使用 ref 来管理表单数据:

vue
<script setup>
import { ref, computed } from 'vue'

const message = ref('')
const checked = ref(false)
const checkedNames = ref([])
const picked = ref('')
const selected = ref('')
const multiSelect = ref([])

// 使用计算属性进行表单验证
const isValid = computed(() => {
  return message.value.trim().length > 0 && checked.value
})

function submitForm() {
  if (isValid.value) {
    console.log({
      message: message.value,
      checked: checked.value,
      checkedNames: checkedNames.value,
      picked: picked.value,
      selected: selected.value,
      multiSelect: multiSelect.value
    })
  }
}
</script>

<template>
  <form @submit.prevent="submitForm">
    <div>
      <input v-model="message" placeholder="Message">
      <span v-if="!message.trim()">Required</span>
    </div>
    
    <div>
      <input type="checkbox" v-model="checked" id="agree">
      <label for="agree">Agree to terms</label>
    </div>
    
    <div>
      <input type="checkbox" value="Vue" v-model="checkedNames" id="vue">
      <label for="vue">Vue</label>
      <input type="checkbox" value="React" v-model="checkedNames" id="react">
      <label for="react">React</label>
    </div>
    
    <div>
      <input type="radio" value="option1" v-model="picked" id="option1">
      <label for="option1">Option 1</label>
      <input type="radio" value="option2" v-model="picked" id="option2">
      <label for="option2">Option 2</label>
    </div>
    
    <div>
      <select v-model="selected">
        <option value="">Select</option>
        <option value="option1">Option 1</option>
        <option value="option2">Option 2</option>
      </select>
    </div>
    
    <div>
      <select v-model="multiSelect" multiple>
        <option value="option1">Option 1</option>
        <option value="option2">Option 2</option>
        <option value="option3">Option 3</option>
      </select>
    </div>
    
    <button type="submit" :disabled="!isValid">Submit</button>
  </form>
</template>

表单验证

简单验证示例

vue
<script setup>
import { ref, computed } from 'vue'

const email = ref('')
const password = ref('')
const agree = ref(false)

const emailError = computed(() => {
  if (!email.value) return 'Email is required'
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  if (!emailRegex.test(email.value)) return 'Invalid email format'
  return ''
})

const passwordError = computed(() => {
  if (!password.value) return 'Password is required'
  if (password.value.length < 6) return 'Password must be at least 6 characters'
  return ''
})

const formValid = computed(() => {
  return !emailError.value && !passwordError.value && agree.value
})
</script>

<template>
  <form>
    <div class="form-group">
      <label for="email">Email:</label>
      <input 
        id="email"
        v-model="email" 
        type="email" 
        :class="{ error: emailError }"
        placeholder="Enter your email"
      >
      <span v-if="emailError" class="error">{{ emailError }}</span>
    </div>
    
    <div class="form-group">
      <label for="password">Password:</label>
      <input 
        id="password"
        v-model="password" 
        type="password" 
        :class="{ error: passwordError }"
        placeholder="Enter your password"
      >
      <span v-if="passwordError" class="error">{{ passwordError }}</span>
    </div>
    
    <div class="form-group">
      <label>
        <input type="checkbox" v-model="agree">
        I agree to the terms and conditions
      </label>
    </div>
    
    <button type="button" :disabled="!formValid" @click="submitForm">
      Submit
    </button>
  </form>
</template>

<style>
.form-group {
  margin-bottom: 15px;
}
.error {
  border: 1px solid red;
}
.error-message {
  color: red;
  font-size: 14px;
}
</style>

高级表单处理

动态表单

vue
<script setup>
import { ref, reactive } from 'vue'

const formData = reactive({
  name: '',
  email: '',
  age: null,
  gender: '',
  skills: [],
  bio: ''
})

const skillsOptions = ['JavaScript', 'Vue.js', 'React', 'Node.js', 'Python']

function submitForm() {
  console.log('Form submitted:', { ...formData })
  // 提交表单逻辑
}

function resetForm() {
  Object.keys(formData).forEach(key => {
    if (Array.isArray(formData[key])) {
      formData[key] = []
    } else {
      formData[key] = typeof formData[key] === 'number' ? null : ''
    }
  })
}
</script>

<template>
  <form @submit.prevent="submitForm">
    <div class="form-row">
      <label>Name:</label>
      <input v-model="formData.name" type="text" placeholder="Your name">
    </div>
    
    <div class="form-row">
      <label>Email:</label>
      <input v-model="formData.email" type="email" placeholder="Your email">
    </div>
    
    <div class="form-row">
      <label>Age:</label>
      <input v-model.number="formData.age" type="number" placeholder="Your age">
    </div>
    
    <div class="form-row">
      <label>Gender:</label>
      <select v-model="formData.gender">
        <option value="">Select</option>
        <option value="male">Male</option>
        <option value="female">Female</option>
        <option value="other">Other</option>
      </select>
    </div>
    
    <div class="form-row">
      <label>Skills:</label>
      <div class="checkbox-group">
        <label v-for="skill in skillsOptions" :key="skill">
          <input 
            type="checkbox" 
            :value="skill" 
            v-model="formData.skills"
          >
          {{ skill }}
        </label>
      </div>
    </div>
    
    <div class="form-row">
      <label>Bio:</label>
      <textarea 
        v-model="formData.bio" 
        placeholder="Tell us about yourself"
        rows="4"
      ></textarea>
    </div>
    
    <div class="form-actions">
      <button type="submit">Submit</button>
      <button type="button" @click="resetForm">Reset</button>
    </div>
  </form>
</template>

<style>
.form-row {
  margin-bottom: 15px;
}
.checkbox-group label {
  display: inline-block;
  margin-right: 15px;
}
.form-actions {
  margin-top: 20px;
}
.form-actions button {
  margin-right: 10px;
  padding: 8px 16px;
}
</style>

最佳实践

1. 使用适当的输入类型

vue
<template>
  <div>
    <input type="email" v-model="email" placeholder="Email">
    <input type="number" v-model.number="age" placeholder="Age">
    <input type="tel" v-model="phone" placeholder="Phone">
    <input type="url" v-model="website" placeholder="Website">
    <input type="date" v-model="date" placeholder="Date">
  </div>
</template>

2. 合理使用修饰符

vue
<template>
  <div>
    <!-- 自动转换为数字 -->
    <input v-model.number="age" type="number">
    
    <!-- 自动去除首尾空格 -->
    <input v-model.trim="username" type="text">
    
    <!-- 在change事件后更新,而不是input事件 -->
    <input v-model.lazy="searchQuery" type="text">
  </div>
</template>

3. 处理表单提交

vue
<script setup>
import { ref } from 'vue'

const form = ref({
  email: '',
  password: ''
})
const submitting = ref(false)

async function handleSubmit() {
  if (!form.value.email || !form.value.password) {
    alert('Please fill in all fields')
    return
  }
  
  submitting.value = true
  try {
    // 模拟API调用
    await new Promise(resolve => setTimeout(resolve, 1000))
    console.log('Form submitted successfully')
  } catch (error) {
    console.error('Form submission failed:', error)
  } finally {
    submitting.value = false
  }
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input 
      v-model="form.email" 
      type="email" 
      placeholder="Email" 
      required
    >
    <input 
      v-model="form.password" 
      type="password" 
      placeholder="Password" 
      required
    >
    <button type="submit" :disabled="submitting">
      {{ submitting ? 'Submitting...' : 'Submit' }}
    </button>
  </form>
</template>

表单输入绑定是Vue.js中实现用户数据交互的重要功能,通过合理使用v-model指令及其修饰符,可以轻松实现双向数据绑定,构建出响应迅速、验证完善的表单界面。