一、v-model的作用和使用场景

1.1 v-model指令介绍

  • 期望的绑定值类型:根据表单输入元素或组件输出的值而变化

  • 可以下下面元素使用:

    • <input>
    • <select>
    • <testarea>
    • components
    • <checkbox>
    • <radio>
    • <select>

    表单输入元素都是可以使用的

  • v-model支持的修饰符:

    • .lazy——监听 change 事件而不是 input
    • .number ——将输入的合法符串转为数字
    • .trim—移除输入内容两端空格

1.2 v-model指令的作用

在表单输入元素或组件上创建双向绑定。

它会根据控件类型自动选取正确的方法来更新元素。

它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理。

毕竟表单提交是开发中非常常见的功能,也是和用户交互的重要手段。

用户在登录、注册时需要提交账号密码,检索、新建、更新数据时,需要提交数据,这些都离不开表单提交。

1.3 什么是双向绑定

Vue中的的双向数据绑定是指model模型(Vuedata下定义的变量)和view(视图)的双向绑定。

其中一个发生改变,另一个也会更新改变。

但是更通俗和常用的讲就是当表单元素的值发生变化时。和它绑定的Vue中的data变量也发生改变。,

二、v-model的基本使用

2.1 代码示例和解析

<div id="app">

    <!-- 1.使用v-model实现input的双向绑定 -->
    <input type="text" v-model="message">
 	<h2>input: {{message}}</h2>
    <hr>
    
    <!-- 2.使用v-model实现textarea的双向绑定 -->
    <textarea cols="30" rows="10" v-model="content"></textarea>
    <h2>textarea: {{content}}</h2>
    <hr>
    
    <!-- 3.使用v-model实现checkbox的双向绑定 -->
    <!-- 3.1 checkbox单选框: 绑定到属性中的值是一个Boolean -->
    <!-- label的作用是点击label中的文本时,相当于点击到绑定的input元素 -->
    <label for="agree">
        <input id="agree" type="checkbox" v-model="isAgree"> 同意协议
    </label>
    <h2>checkbox单选: {{isAgree}}</h2>
    <hr>
    
    <!-- 3.2 checkbox多选框: 绑定到属性中的值是一个Array -->
    <!--  checkbox单选框; 绑定到属性中的值是单选框的value -->
    <div class="hobbies">
        <h2>请选择你的爱好:</h2>
        <label for="sing">
            <input id="sing" type="checkbox" v-model="hobbies" value="sing"></label>
        <label for="jump">
            <input id="jump" type="checkbox" v-model="hobbies" value="jump"></label>
        <h2>爱好: {{hobbies}}</h2>
    </div>
    <hr>
    
    
    <!-- 4.使用v-model实现radio的双向绑定 -->
    <!-- radio单选框: 绑定到属性中的值是单选框的value -->
    <div class="gender">
        <label for="male">
            <input id="male" type="radio" v-model="gender" value="male"></label>
        <label for="female">
            <input id="female" type="radio" v-model="gender" value="female"></label>
        <h2>性别: {{gender}}</h2>
    </div>
    <hr>
    
    
    <!-- 4.使用v-model实现select的双向绑定 -->
 	<!-- 4.1 select单选框的值绑定 -->
    <select v-model="fruit">
        <option v-for="item in allFruits" :key="item.value" :value="item.value">
            {{item.text}}
        </option>
    </select>
    <h2>单选: {{fruit}}</h2>
	<!-- 4.2 select多选框的值绑定 -->
    <select multiple size="3" v-model="fruits">
        <option v-for="item in allFruits" :key="item.value" :value="item.value">
            {{item.text}}
        </option>
    </select>
    <h2>多选: {{fruits}}</h2>
    <hr>
</div>

<script src="../lib/vue.js"></script>
<script>
    // 1.创建app
    const app = Vue.createApp({
        data() {
            return {
                message: "Hello Model",		//v-model绑定的input数据
                content: "",				//v-model绑定的textarea数据
                isAgree: false,				//v-model绑定的checkbox单选框数据 
                hobbies: []					//v-model绑定的checkbox多选框数据
                gender: "female"			//v-model绑定的radio单选框数据
                fruit: "orange",			//v-model绑定的select单选框数据
                fruits: [],					//v-model绑定的select多选框数据
                             
                allFruits: [
                    { value: "apple", text: "苹果" },
                    { value: "orange", text: "橘子" },
                    { value: "banana", text: "香蕉" },
                ],						 	// 水果
            }
        }
    })

    // 2.挂载app
    app.mount("#app")
</script>

可以看到,使用v-model的方式非常简单:

  • 在支持v-model的表单输入元素上加上v-model指令
  • Vuedata选项API中定义数据变量,赋值给v-model就实现了数据的双向绑定

这些也都是对基础的表单元素进行操作。实际开发大多会使用各种各样的组件库开发。

但是使用的方式和原理都是一样的。

2.2 v-model和值绑定:

所谓值绑定,其实并不是很高深的东西,只是Vue官方提供的一个概念。

意思就是表单元素中的value值并不是写死的,而是来自于服务器或者配置文件。

我们就可以先将值请求下来,绑定到data返回的对象中,

再使用条件渲染指令和列表渲染指令把值动态绑定到表单元素上,最后通过v-bind指令来进行绑定。

这个过程就是值绑定

例如上面代码中select的绑定方式:

<div id="app"> 
 	<!-- select单选框的值绑定 -->
    <select v-model="fruit">
        <option v-for="item in allFruits" :key="item.value" :value="item.value">
            {{item.text}}
        </option>
    </select>
    <h2>单选: {{fruit}}</h2>

</div>

<script src="../lib/vue.js"></script>
<script>
    // 1.创建app
    const app = Vue.createApp({
        data() {
            return {
                fruit: "orange",			//v-model绑定的select单选框数据      
                allFruits: [
                    { value: "apple", text: "苹果" },
                    { value: "orange", text: "橘子" },
                    { value: "banana", text: "香蕉" },
                ],						 	// 水果
            }
        }
    })

    // 2.挂载app
    app.mount("#app")
</script>

在这段代码中,<select><option>的值并不是直接写死在表单元素上的。

它们的值来自allFruits数组,这个数组可能来自于服务器或者配置文件。

这也是开发中常见的情况。

三、v-model的修饰符

3.1 v-model支持的修饰符:

  • .lazy ——监听 change 事件而不是 input

    • 默认情况下,v-model在进行双向绑定时,绑定的是input事件。

      那么会在每次内容输入后就将最新的值和绑定的属性进行同步。

    • 如果在v-model后跟上lazy修饰符,那么会将绑定的事件切换为 change 事件。

      只有在提交(或者回车)时才会触发。

  • .number ——将输入的合法符串转为数字

  • .trim ——移除输入内容两端空格

3.2 代码示例

<div id="app">
    <!-- 1.lazy: 绑定change事件  -->
    <input type="text" v-model.lazy="message">
    <h2>message: {{message}}</h2>
    <hr>

    <!-- 2.number: 自动将内容转换成数字 -->
    <!-- 
		这里有个冷知识:
		vue2.x中 如果input中输入的是数字,v-model也会自动把数字转为字符串,除非加上.number修饰符
		vue2.x中 如果input中输入的是数字,将可以直接获取到数字,而不用再加.number修饰符
	-->
    <input type="text" v-model.number="counter">
    <h2>counter:{{counter}}-{{typeof counter}}</h2>

    <input type="number" v-model="counter2">
    <h2>counter2:{{counter2}}-{{typeof counter2}}</h2>
    <hr>

    <!-- 3.trim: 去除收尾的空格 -->
    <input type="text" v-model.trim="content">
    <h2>content: {{content}}</h2>
    <hr>

    <!-- 4.使用多个修饰符 -->
    <input type="text" v-model.lazy.trim="content">
    <h2>content: {{content}}</h2>
</div>

<script src="../lib/vue.js"></script>
<script>
    // 1.创建app
    const app = Vue.createApp({
        // data: option api
        data() {
            return {
                message: "Hello Vue",
                counter: 0,
                counter2: 0,
                content: ""
            }
        },
        watch: {
            content(newValue) {
                console.log("content:", newValue)
            }
        }
    })

    // 2.挂载app
    app.mount("#app")
</script>

四、手写v-model原理

官方文档中有提到,v-model的原理其实是背后有两个操作:

  • v-bind绑定value属性的值

  • v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中

依旧上面的原理,可以手写一个v-model的实现:

<div id="app">
    <!-- 
		手动的实现双向绑定:
			先使用v-bind语法糖把message绑定到input的value上
			再使用v-on语法糖绑定input元素的input事件
	-->
    <input type="text" :value="message" @input="change">
</div>

<script src="../lib/vue.js"></script>
<script>
    // 1.创建app
    const app = Vue.createApp({
        data() {
            return {
                message: "Hello Model"
            }
        },
        methods: {
            change(event) {
                // 获取当前input表单元素中的内容
                this.message = event.target.value
            }
        }
    })

    // 2.挂载app
    app.mount("#app")
</script>

至于如果程序中的message发送变化,Vue会有另一部分代码监听数据的变化,并把变化的数据渲染到视图上。

再结合v-model就完成了Vue中数据的双向绑定。

五、v-model用于组件上

5.1 v-model在组件上的基本使用

通过上面的说明可以知道,表单元素上的v-model使用方式如下:

<input v-model="searchText" />

上面的代码其实等价于下面这段 (编译器会对 v-model 进行展开):

<input
  :value="searchText"
  @input="searchText = $event.target.value"
/>

而当使用在一个组件上时,v-model 会被展开为如下的形式:

<CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>

因此<CustomInput> 组件内部需要做两件事:

  • 将内部原生 input 元素的 value属性绑定到 modelValueprop组件上

  • 输入新的值时在 input 元素上触发 update:modelValue 事件

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

<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>


5.2 自定义组件中处理v-model 的参数

默认v-model 在组件上都是使用 modelValue 作为 prop,并以 update:modelValue 作为对应的事件。

我们可以通过给 v-model 指定一个参数来更改这些名字。

<MyComponent v-model:title="bookTitle" />

此时子组件应声明一个 title prop,并通过触发 update:title 事件更新父组件值:

<!-- MyComponent.vue -->
<template>
  <input
    type="text"
    :value="title"
    @input="$emit('update:title', $event.target.value)"
  />
</template>

<script>
export default {
  props: ['title'],
  emits: ['update:title']
}
</script>

5.3 自定义组件中处理多个v-model 绑定

我们可以在一个组件上创建多个 v-model 双向绑定,每一个 v-model 都会同步不同的prop

<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>
<script>
export default {
  props: {
    firstName: String,
    lastName: String
  },
  emits: ['update:firstName', 'update:lastName']
}
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>

5.4 自定义组件中处理 v-model 修饰符

v-model 有一些内置的修饰符。例如 .trim.number.lazy

在某些场景下,你可能想要一个自定义组件的 v-model 支持自定义的修饰符。

例如创建一个自定义的修饰符 capitalize,它会自动将 v-model 绑定输入的字符串值第一个字母转为大写:

<MyComponent v-model.capitalize="myText" />

组件的 v-model 上所添加的修饰符,可以通过 modelModifiers prop 在组件内访问到。

在下面的组件中,声明了 modelModifiers 这个 prop,它的默认值是一个空对象:

<script>
export default {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  created() {
    console.log(this.modelModifiers) // { capitalize: true }
  }
}
</script>

<template>
  <input
    type="text"
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

注意

组件prop中的 modelModifiers的 包含了 capitalize 且其值为 true

因为它在模板中的 v-model 绑定上被使用了。

有了 modelModifiers 这个 prop,我们就可以在原生事件侦听函数中检查它的值。

然后决定触发的自定义事件中要向父组件传递什么值。

<script>
export default {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  methods: {
    emitValue(e) {
      let value = e.target.value
      // 如果有capitalize的值为true,则做一些对应的操作
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  }
}
</script>

<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>

对于又有参数又有修饰符的 v-model 绑定,生成的 prop 名将是 arg + "Modifiers"

示例:

<MyComponent v-model:title.capitalize="myText">
export default {
  props: ['title', 'titleModifiers'],
  emits: ['update:title'],
  created() {
    console.log(this.titleModifiers) // { capitalize: true }
  }
}

六、v-model官方文档

https://cn.vuejs.org/api/built-in-directives.html#v-model
Logo

前往低代码交流专区

更多推荐