Vue.js表单输入绑定

本章重点:掌握v-model指令实现表单元素与Vue数据的双向绑定

v-model简介

v-model指令在表单输入元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。


<!-- 基础语法 -->
<input v-model="message" placeholder="编辑我">
<p>消息是: {{ message }}</p>

<!-- 等价于 -->
<input :value="message" @input="message = $event.target.value">
                        

文本输入框

使用v-model绑定到<input>元素:

单行文本

<div id="text-example">
    <input v-model="message" placeholder="请输入内容">
    <p>输入的内容是: {{ message }}</p>

    <!-- 不同类型的输入框 -->
    <div class="mb-2">
        <label>用户名:</label>
        <input v-model="username" type="text" class="form-control">
    </div>

    <div class="mb-2">
        <label>邮箱:</label>
        <input v-model="email" type="email" class="form-control">
    </div>

    <div class="mb-2">
        <label>密码:</label>
        <input v-model="password" type="password" class="form-control">
    </div>

    <div class="mb-2">
        <label>网址:</label>
        <input v-model="url" type="url" class="form-control">
    </div>

    <div class="mb-2">
        <label>数字:</label>
        <input v-model="number" type="number" class="form-control">
    </div>

    <div class="mb-2">
        <label>范围: {{ rangeValue }}</label>
        <input v-model="rangeValue" type="range" min="0" max="100" class="form-range">
    </div>
</div>

<script>
new Vue({
    el: '#text-example',
    data: {
        message: '',
        username: '',
        email: '',
        password: '',
        url: '',
        number: 0,
        rangeValue: 50
    }
});
</script>
                            

多行文本

绑定到<textarea>元素:


<div id="textarea-example">
    <span>多行消息是:</span>
    <p style="white-space: pre-line;">{{ message }}</p>
    <br>

    <textarea v-model="message" placeholder="添加多行文本"></textarea>

    <!-- 注意:在<textarea>中使用插值无效 -->
    <!-- 错误示例 -->
    <textarea>{{ message }}</textarea> <!-- 不会工作! -->

    <!-- 正确示例 -->
    <textarea v-model="message"></textarea>
</div>

<script>
new Vue({
    el: '#textarea-example',
    data: {
        message: '这是初始文本\n第二行文本'
    }
});
</script>
                        
重要:<textarea>{{ text }}</textarea>不会工作! 应该使用v-model来绑定数据。

复选框

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

复选框示例

<div id="checkbox-example">
    <!-- 单个复选框 -->
    <div class="form-check">
        <input type="checkbox" id="agree" v-model="checked" class="form-check-input">
        <label for="agree" class="form-check-label">我同意条款</label>
    </div>
    <p>选择状态: {{ checked }}</p>

    <!-- 多个复选框绑定到数组 -->
    <div class="mt-3">
        <p>选择你喜欢的编程语言:</p>
        <div class="form-check">
            <input type="checkbox" id="javascript" value="JavaScript" v-model="languages" class="form-check-input">
            <label for="javascript" class="form-check-label">JavaScript</label>
        </div>
        <div class="form-check">
            <input type="checkbox" id="python" value="Python" v-model="languages" class="form-check-input">
            <label for="python" class="form-check-label">Python</label>
        </div>
        <div class="form-check">
            <input type="checkbox" id="java" value="Java" v-model="languages" class="form-check-input">
            <label for="java" class="form-check-label">Java</label>
        </div>
        <div class="form-check">
            <input type="checkbox" id="php" value="PHP" v-model="languages" class="form-check-input">
            <label for="php" class="form-check-label">PHP</label>
        </div>
        <p>已选择: {{ languages }}</p>
    </div>

    <!-- 自定义值的复选框 -->
    <div class="mt-3">
        <input type="checkbox" v-model="toggle" true-value="是" false-value="否">
        <label>开关状态: {{ toggle }}</label>
    </div>
</div>

<script>
new Vue({
    el: '#checkbox-example',
    data: {
        checked: false,
        languages: [],
        toggle: '否'
    }
});
</script>
                            

单选按钮

多个单选按钮绑定到同一个数据属性:


<div id="radio-example">
    <p>选择你喜欢的框架:</p>

    <div class="form-check">
        <input type="radio" id="vue" value="Vue.js" v-model="framework" class="form-check-input">
        <label for="vue" class="form-check-label">Vue.js</label>
    </div>

    <div class="form-check">
        <input type="radio" id="react" value="React" v-model="framework" class="form-check-input">
        <label for="react" class="form-check-label">React</label>
    </div>

    <div class="form-check">
        <input type="radio" id="angular" value="Angular" v-model="framework" class="form-check-input">
        <label for="angular" class="form-check-label">Angular</label>
    </div>

    <div class="form-check">
        <input type="radio" id="svelte" value="Svelte" v-model="framework" class="form-check-input">
        <label for="svelte" class="form-check-label">Svelte</label>
    </div>

    <p>你选择的框架是: {{ framework }}</p>
</div>

<script>
new Vue({
    el: '#radio-example',
    data: {
        framework: 'Vue.js' // 默认选择
    }
});
</script>
                        

选择框

单选选择框绑定到字符串,多选选择框绑定到数组:

选择框示例

<div id="select-example">
    <!-- 单选选择框 -->
    <div class="mb-3">
        <label>选择一个城市:</label>
        <select v-model="selectedCity" class="form-select">
            <option disabled value="">请选择</option>
            <option value="beijing">北京</option>
            <option value="shanghai">上海</option>
            <option value="guangzhou">广州</option>
            <option value="shenzhen">深圳</option>
        </select>
        <p>选择的城市: {{ selectedCity }}</p>
    </div>

    <!-- 多选选择框 -->
    <div class="mb-3">
        <label>选择多个技能 (按住Ctrl/Cmd选择):</label>
        <select v-model="selectedSkills" multiple class="form-select">
            <option value="html">HTML</option>
            <option value="css">CSS</option>
            <option value="javascript">JavaScript</option>
            <option value="vue">Vue.js</option>
            <option value="react">React</option>
            <option value="node">Node.js</option>
        </select>
        <p>选择的技能: {{ selectedSkills }}</option>
    </div>

    <!-- 使用v-for动态渲染选项 -->
    <div class="mb-3">
        <label>选择一个框架:</label>
        <select v-model="selectedFramework" class="form-select">
            <option v-for="framework in frameworks" :value="framework.value">
                {{ framework.name }}
            </option>
        </select>
        <p>选择的框架: {{ selectedFramework }}</p>
    </div>
</div>

<script>
new Vue({
    el: '#select-example',
    data: {
        selectedCity: '',
        selectedSkills: [],
        selectedFramework: '',
        frameworks: [
            { value: 'vue', name: 'Vue.js' },
            { value: 'react', name: 'React' },
            { value: 'angular', name: 'Angular' },
            { value: 'svelte', name: 'Svelte' }
        ]
    }
});
</script>
                            
注意:如果v-model表达式的初始值未能匹配任何选项,<select>元素将被渲染为"未选中"状态。 在iOS中,这会使用户无法选择第一个选项。建议提供一个值为空的禁用选项。

值绑定

对于单选按钮、复选框及选择框的选项,v-model绑定的值通常是静态字符串,但也可以绑定动态值:

静态值绑定

<!-- 单选按钮 -->
<input type="radio" v-model="pick" value="a">

<!-- 复选框 -->
<input type="checkbox" v-model="toggle">

<!-- 选择框选项 -->
<select v-model="selected">
    <option value="abc">ABC</option>
</select>
                                
动态值绑定

<!-- 单选按钮绑定动态值 -->
<input type="radio" v-model="pick" :value="dynamicValue">

<!-- 复选框绑定动态值 -->
<input type="checkbox" v-model="toggle" :true-value="dynamicTrueValue" :false-value="dynamicFalseValue">

<!-- 选择框选项绑定动态值 -->
<select v-model="selected">
    <option :value="dynamicValue">
        {{ dynamicText }}
    </option>
</select>
                                

修饰符

v-model指令提供了三个修饰符来自动处理用户输入:

修饰符 描述 示例 应用场景
.lazy 转为在change事件后同步数据 v-model.lazy="msg" 避免频繁更新,提升性能
.number 自动将用户的输入值转为数值类型 v-model.number="age" 处理数字输入,避免字符串类型
.trim 自动过滤用户输入的首尾空白字符 v-model.trim="name" 用户名、邮箱等需要去除空格的场景

<div id="modifier-example">
    <!-- .lazy 修饰符 -->
    <div class="mb-3">
        <label>实时更新:</label>
        <input v-model="message1" placeholder="输入时实时更新" class="form-control">
        <p>实时值: "{{ message1 }}"</p>

        <label>延迟更新(.lazy):</label>
        <input v-model.lazy="message2" placeholder="失焦后更新" class="form-control">
        <p>延迟值: "{{ message2 }}"</p>
    </div>

    <!-- .number 修饰符 -->
    <div class="mb-3">
        <label>年龄(字符串):</label>
        <input v-model="age1" type="number" class="form-control">
        <p>类型: {{ typeof age1 }}, 值: {{ age1 }}</p>

        <label>年龄(数字):</label>
        <input v-model.number="age2" type="number" class="form-control">
        <p>类型: {{ typeof age2 }}, 值: {{ age2 }}</p>
    </div>

    <!-- .trim 修饰符 -->
    <div class="mb-3">
        <label>用户名(未修剪):</label>
        <input v-model="username1" placeholder="输入带空格的用户名" class="form-control">
        <p>原始长度: {{ username1.length }}</p>

        <label>用户名(修剪后):</label>
        <input v-model.trim="username2" placeholder="输入带空格的用户名" class="form-control">
        <p>修剪后长度: {{ username2.length }}</p>
    </div>
</div>

<script>
new Vue({
    el: '#modifier-example',
    data: {
        message1: '',
        message2: '',
        age1: '',
        age2: '',
        username1: '',
        username2: ''
    }
});
</script>
                        

完整示例

综合使用各种表单元素和验证:

用户注册表单示例

<div id="complete-form-example">
    <form @submit.prevent="submitForm">
        <!-- 文本输入 -->
        <div class="mb-3">
            <label for="name" class="form-label">姓名</label>
            <input type="text" id="name" v-model.trim="form.name"
                   class="form-control" :class="{ 'is-invalid': errors.name }"
                   placeholder="请输入姓名" required>
            <div class="invalid-feedback" v-if="errors.name">
                {{ errors.name }}
            </div>
        </div>

        <!-- 邮箱输入 -->
        <div class="mb-3">
            <label for="email" class="form-label">邮箱地址</label>
            <input type="email" id="email" v-model.trim="form.email"
                   class="form-control" :class="{ 'is-invalid': errors.email }"
                   placeholder="name@example.com" required>
            <div class="invalid-feedback" v-if="errors.email">
                {{ errors.email }}
            </div>
        </div>

        <!-- 密码输入 -->
        <div class="mb-3">
            <label for="password" class="form-label">密码</label>
            <input type="password" id="password" v-model="form.password"
                   class="form-control" :class="{ 'is-invalid': errors.password }"
                   placeholder="至少6个字符" required>
            <div class="invalid-feedback" v-if="errors.password">
                {{ errors.password }}
            </div>
        </div>

        <!-- 年龄输入 -->
        <div class="mb-3">
            <label for="age" class="form-label">年龄</label>
            <input type="number" id="age" v-model.number="form.age"
                   class="form-control" :class="{ 'is-invalid': errors.age }"
                   min="0" max="150">
            <div class="invalid-feedback" v-if="errors.age">
                {{ errors.age }}
            </div>
        </div>

        <!-- 性别单选按钮 -->
        <div class="mb-3">
            <label class="form-label">性别</label>
            <div class="form-check">
                <input type="radio" id="male" value="male" v-model="form.gender" class="form-check-input">
                <label for="male" class="form-check-label">男</label>
            </div>
            <div class="form-check">
                <input type="radio" id="female" value="female" v-model="form.gender" class="form-check-input">
                <label for="female" class="form-check-label">女</label>
            </div>
            <div class="form-check">
                <input type="radio" id="other" value="other" v-model="form.gender" class="form-check-input">
                <label for="other" class="form-check-label">其他</label>
            </div>
        </div>

        <!-- 兴趣复选框 -->
        <div class="mb-3">
            <label class="form-label">兴趣爱好</label>
            <div class="form-check">
                <input type="checkbox" id="reading" value="reading" v-model="form.hobbies" class="form-check-input">
                <label for="reading" class="form-check-label">阅读</label>
            </div>
            <div class="form-check">
                <input type="checkbox" id="sports" value="sports" v-model="form.hobbies" class="form-check-input">
                <label for="sports" class="form-check-label">运动</label>
            </div>
            <div class="form-check">
                <input type="checkbox" id="music" value="music" v-model="form.hobbies" class="form-check-input">
                <label for="music" class="form-check-label">音乐</label>
            </div>
            <div class="form-check">
                <input type="checkbox" id="travel" value="travel" v-model="form.hobbies" class="form-check-input">
                <label for="travel" class="form-check-label">旅行</label>
            </div>
        </div>

        <!-- 城市选择 -->
        <div class="mb-3">
            <label for="city" class="form-label">所在城市</label>
            <select id="city" v-model="form.city" class="form-select">
                <option value="">请选择城市</option>
                <option value="beijing">北京</option>
                <option value="shanghai">上海</option>
                <option value="guangzhou">广州</option>
                <option value="shenzhen">深圳</option>
                <option value="hangzhou">杭州</option>
            </select>
        </div>

        <!-- 自我介绍 -->
        <div class="mb-3">
            <label for="bio" class="form-label">自我介绍</label>
            <textarea id="bio" v-model.lazy="form.bio" class="form-control"
                      rows="3" placeholder="简单介绍一下自己..."></textarea>
        </div>

        <!-- 协议同意 -->
        <div class="mb-3 form-check">
            <input type="checkbox" id="agree" v-model="form.agree" class="form-check-input">
            <label for="agree" class="form-check-label">我同意用户协议</label>
        </div>

        <!-- 提交按钮 -->
        <button type="submit" class="btn btn-primary" :disabled="isSubmitting">
            {{ isSubmitting ? '提交中...' : '提交注册' }}
        </button>
        <button type="button" @click="resetForm" class="btn btn-secondary ms-2">
            重置表单
        </button>
    </form>

    <!-- 表单数据预览 -->
    <div class="mt-4">
        <h6>表单数据预览:</h6>
        <pre>{{ JSON.stringify(form, null, 2) }}</pre>
    </div>
</div>

<script>
new Vue({
    el: '#complete-form-example',
    data: {
        isSubmitting: false,
        form: {
            name: '',
            email: '',
            password: '',
            age: 18,
            gender: 'male',
            hobbies: [],
            city: '',
            bio: '',
            agree: false
        },
        errors: {}
    },
    methods: {
        validateForm() {
            this.errors = {};

            // 验证姓名
            if (!this.form.name.trim()) {
                this.errors.name = '姓名不能为空';
            }

            // 验证邮箱
            if (!this.form.email) {
                this.errors.email = '邮箱不能为空';
            } else if (!this.isValidEmail(this.form.email)) {
                this.errors.email = '请输入有效的邮箱地址';
            }

            // 验证密码
            if (!this.form.password) {
                this.errors.password = '密码不能为空';
            } else if (this.form.password.length < 6) {
                this.errors.password = '密码至少需要6个字符';
            }

            // 验证年龄
            if (this.form.age < 0) {
                this.errors.age = '年龄不能小于0';
            } else if (this.form.age > 150) {
                this.errors.age = '年龄不能大于150';
            }

            return Object.keys(this.errors).length === 0;
        },

        isValidEmail(email) {
            const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
            return emailRegex.test(email);
        },

        submitForm() {
            if (!this.validateForm()) {
                return;
            }

            if (!this.form.agree) {
                alert('请同意用户协议');
                return;
            }

            this.isSubmitting = true;

            // 模拟API调用
            setTimeout(() => {
                alert('注册成功!\n表单数据已提交。');
                this.isSubmitting = false;

                // 在实际应用中,这里会发送到服务器
                console.log('表单提交:', this.form);
            }, 1000);
        },

        resetForm() {
            this.form = {
                name: '',
                email: '',
                password: '',
                age: 18,
                gender: 'male',
                hobbies: [],
                city: '',
                bio: '',
                agree: false
            };
            this.errors = {};
        }
    }
});
</script>
                            

最佳实践

v-model使用建议
  • 合理使用修饰符
    • 使用.trim去除用户输入的首尾空格
    • 使用.number确保数字输入的正确类型
    • 使用.lazy减少频繁更新提升性能
  • 表单验证
    • 及时验证用户输入,提供清晰的错误提示
    • 结合HTML5原生验证和自定义验证
    • 使用计算属性处理复杂的验证逻辑
  • 数据绑定
    • 确保初始数据与表单状态一致
    • 对于复杂表单,使用对象存储表单数据
    • 避免在模板中直接修改复杂数据类型
  • 性能优化
    • 大型表单使用.lazy修饰符
    • 考虑使用防抖/节流处理频繁输入
    • 合理组织表单组件,避免不必要的重新渲染