# vue实例

# 构造器

  • 每个vue.js应用都是通过构造函数Vue创建一个Vue的根实例启动的
var app = new Vue({
  /* 选项 */
})
1
2
3

注意:在实例化Vue时,需要传入一个选项对象,它可以包含数据、模板挂载元素、方法、生命周期钩子等选项

  • 可以扩展Vue构造器,从而用预定义选项创建可复用的组件构造器。而且所有的Vue.js组件其实都是被扩展的Vue实例
var myComponent=Vue.extend({
  //扩展选项
});
var myComponentInstance = new MyComponent();
1
2
3
4

# 属性和方法

  • 每个 Vue 实例都会代理其 data对象里所有的属性
var data = { a: 1 }
  var vm = new Vue({
  data: data
});
vm.a === data.a // -> true
// 设置属性也会影响到原始数据
vm.a = 2
data.a // -> 2
// ... 反之亦然
data.a = 3
vm.a // -> 3
1
2
3
4
5
6
7
8
9
10
11
  • 被代理的属性是响应的,也就是说值的任何改变都是触发视图的重新渲染。如果在实例创建之后添加新的属性到实例上,它不会触发视图更新。
  • 除了 data 属性, Vue 实例暴露了一些有用的实例属性与方法。这些属性与方法都有前缀 $,以便与代理的 data 属性区分
var data = { a: 1 }
  var vm = new Vue({
  el: '#example',
  data: data
});
vm.$data === data // -> true
vm.$el === document.getElementById('example') // -> true
// $watch 是一个实例方法,对data中的a属性进行监听
vm.$watch('a', function (newVal, oldVal) {
  // 这个回调将在 `vm.a` 改变后调用
});
1
2
3
4
5
6
7
8
9
10
11

# 生命周期钩子

  • 每个Vue实例在被创建之前都要经过一系列的初始化过程。例如,实例需要配置数据观测(data observer)、编译模板(complie template)、挂载实例到DOM(mounted)、然后在数据变化时更DOM(update)。在这个过程中,实例也会调用一些 生命周期钩子,这就给我们提供了执行自定义逻辑的机会。例如,created这个钩子在实例被创建之后被调用
Vue.createApp({
  data() {
    return { count: 1}
  },
  created() {
    // `this` 指向 vm 实例
    console.log('count is: ' + this.count) // => "count is: 1"
  }
})
1
2
3
4
5
6
7
8
9

也有一些其它的钩子,在实例生命周期的不同阶段被调用,如 mounted和 unmounted。生命周期钩子的 this 上下文指向调用它的当前活动实例。

TIP

不要在选项 property 或回调上使用箭头函数 (opens new window),比如 created: () => console.log(this.a)vm.$watch('a', newValue => this.myMethod())。因为箭头函数并没有 thisthis 会作为变量一直向上级词法作用域查找,直至找到为止,经常导致 Uncaught TypeError: Cannot read property of undefinedUncaught TypeError: this.myMethod is not a function 之类的错误。

# 生命周期图示

下图展示了实例的生命周期。我们不需要立马弄明白所有的东西,不过随着不断学习和使用,它的参考价值会越来越高。 lifecycle

# 插值操作

# Mustache语法

  • 作用:通过Mustache语法(也就是双大括号)将data中的文本数据,插入到HTML
  • 使用代码如下: vue_base_01

# v-once

  • 需求:某些情况下,我们可能不希望界面随意的跟随改变,我们就可以使用一个Vue的指令。
  • 使用规则:
    • 该指令后面不需要跟任何表达式
    • p该指令表示元素和组件(组件后面才会学习)只渲染一次,不会随着数据的改变而改变。
  • 使用代码如下: vue_base_02

# v-html

  • 需求:某些情况下,我们从服务器请求到的数据本身就是一个HTML代码
    • 如果我们直接通过{{}}来输出,会将HTML代码也一起输出
    • 但是我们可能希望的是按照HTML格式进行解析,并且显示对应的内容
  • 使用规则:
    • 该指令后面往往会跟上一个string类型
    • 会将string的html解析出来并且进行渲染
  • 使用代码如下: vue_base_03

# v-text

  • 作用:v-text作用和Mustache比较相似:都是用于将数据显示在界面中
  • 使用规则:通常情况下,接受一个string类型
  • 使用代码如下: vue_base_04

# v-pre

  • 作用:nv-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。
  • 使用代码如下:第一个h2元素中的内容会被编译解析出来对应的内容;第二个h2元素中会直接显示为string文本。 vue_base_05

# v-cloak

  • 需求:某些情况下,我们浏览器可能会直接显然出未编译的Mustache标签
  • 使用代码: vue_base_06

# 绑定属性

# v-bind介绍

  • 需求:除了内容需要动态来决定外,某些属性我们也希望动态来绑定。比如动态绑定a元素的href属性,动态绑定img元素的src属性

# v-bind基础用法

  • 作用:用于绑定一个或多个属性值,或者向另一个组件传递props值
  • 使用代码如下: image-20210715155138419

# v-bind语法糖

  • 使用规则: :的方式
  • 使用代码如下:
<div id="app">
    <a :href="link">Vuejs官网</a>
    <img :src="logoURL"/>
</div>
1
2
3
4

# v-bind绑定classs

  • 绑定对象: class后面跟的是一个对象
    • 1.直接通过{}绑定一个类<h2 :class="{'active': isActive}">Hello World</h2>
    • 2.通过判断,传入多个值<h2 :class="{'active': isActive, 'line': isLine}">Hello World</h2>
    • 3.和普通的类同时存在,并不冲突<h2 class="title" :class="{'active': isActive, 'line': isLine}">Hello World</h2>
    • 4.如果过于复杂,可以放在一个methods或者computed中<h2 class="title" :class="classes">Hello World</h2>
  • 绑定数组: class后面跟的是一个数组
    • 1.直接通过{}绑定一个类<h2 :class="['active']">Hello World</h2>
    • 2.传入多个值<h2 :class=“[‘active’, 'line']">Hello World</h2>
    • 3.和普通的类同时存在,并不冲突<h2 class="title" :class=“[‘active’, 'line']">Hello World</h2>
    • 4.如果过于复杂,可以放在一个methods或者computed中<h2 class="title" :class="classes">Hello World</h2>

# v-bind绑定style

  • 绑定对象::style="{color:currentColor,fontSize:fontSize+'px'}"其中color是key,currentColor是value
  • 绑定数据:<div v-bind:style="[baseStyles,overridingStyles]"></div>
  • 注意点:写CSS属性名的时候,可以使用驼峰式 (camelCase) fontSize ,p或短横线分隔 (kebab-case,记得用单引号括起来) ‘font-size’

# 计算属性

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如,有一个嵌套数组对象:

Vue.createApp({
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14

我们想根据 author 是否已经有一些书来显示不同的消息

<div id="computed-basics">
  <p>Has published books:</p>
  <span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
</div>
1
2
3
4

此时,模板不再是简单的和声明性的。你必须先看一下它,然后才能意识到它执行的计算取决于 author.books。如果要在模板中多次包含此计算,则问题会变得更糟。 所以,对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性

# 基本例子

<div id="computed-basics">
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</div>
1
2
3
4
Vue.createApp({
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  },
  computed: {
    // 计算属性的 getter
    publishedBooksMessage() {
      // `this` 指向 vm 实例
      return this.author.books.length > 0 ? 'Yes' : 'No'
    }
  }
}).mount('#computed-basics')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

这里声明了一个计算属性 publishedBooksMessage。 尝试更改应用程序 databooks 数组的值,你将看到 publishedBooksMessage 如何相应地更改。 你可以像普通属性一样将数据绑定到模板中的计算属性。Vue 知道 vm.publishedBookMessage 依赖于 vm.author.books,因此当 vm.author.books 发生改变时,所有依赖 vm.publishedBookMessage 的绑定也会更新。而且最妙的是我们已经声明的方式创建了这个依赖关系:计算属性的 getter 函数没有副作用,它更易于测试和理解。

# 计算属性缓存 vs 方法

你可能已经注意到我们可以通过在表达式中调用方法来达到同样的效果:

<p>{{ calculateBooksMessage() }}</p>
1
// 在组件中
methods: {
  calculateBooksMessage() {
    return this.author.books.length > 0 ? 'Yes' : 'No'
  }
}
1
2
3
4
5
6

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的反应依赖关系缓存的。计算属性只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 author.books 还没有发生改变,多次访问 publishedBookMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。 这也同样意味着下面的计算属性将不再更新,因为 Date.now () 不是响应式依赖:

computed: {
  now() {
    return Date.now()
  }
}
1
2
3
4
5

相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。 我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 list,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 list。如果没有缓存,我们将不可避免的多次执行 list 的 getter!如果你不希望有缓存,请用 method 来替代。

# 计算属性的 Setter

计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:

// ...
computed: {
  fullName: {
    // getter
    get() {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set(newValue) {
      const names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

现在再运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstNamevm.lastName 也会相应地被更新。

# 事件监听

# v-on介绍

  • 需求:前端开发中,我们需要经常和用于交互。这个时候,我们就必须监听用户发生的时间,比如点击、拖拽、键盘事件等等。
  • 使用规则:
    • 作用:绑定事件监听器
    • 缩写:@
    • 参数:event
  • 使用代码
<div id="app">
    <h2>点击次数:{{counter}}</h2>
    <button v-on:click="counter++">增加按钮</button>
    <!--语法糖格式-->
    <button @click="counter--">减少按钮</button>
</div>
1
2
3
4
5
6

# v-on参数

  • 需求1:如果该方法不需要额外参数,那么方法后的()可以不添加。但是注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去
  • 需求2:如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件。
  • 使用代码如下:
<div id="app">
    <h2>点击次数:{{counter}}</h2>
    <button v-on:click="handleAdd">增加按钮</button>
    <!--语法糖格式-->
    <button @click="handleAddTen(10,$event)">减少按钮</button>
</div>
1
2
3
4
5
6

# v-on修饰符

  • 需求:某些情况下,我们拿到event的目的可能是进行一些事件处理
  • 常见的修饰:
    • .stop - 调用 event.stopPropagation()
    • .prevent - 调用 event.preventDefault()
    • .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
    • .native - 监听组件根元素的原生事件
    • .once - 只触发一次回调
  • 使用代码如下:
<!--停止冒泡-->
<button @click.stop="doThis"></button>
<!--组织默认行为-->
<button @click.prevent="doThis"></button>
<!--阻止默认行为,没有表达式-->
<button @click.prevent></button>
<!--串联修饰符-->
<button @click.stop.prevent="doThis"></button>
<!--键修饰符,键别名-->
<button @keyup.enter="onEnter"></button>
<!--键修饰符,键代码-->
<button @keyup.13="onEnter"></button>
<!--点击回调智慧触发一次-->
<button @click.once ="doThis"></button>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 条件判断

# v-if

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。

<h1 v-if="awesome">Vue is awesome!</h1>
1

也可以用 v-else 添加一个“else 块”:

<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
1
2

<template> 元素上使用 v-if 条件渲染分组 因为 v-if 是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 <template> 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 <template> 元素。

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>
1
2
3
4
5

# v-else

你可以使用 v-else 指令来表示 v-if 的“else 块”:

<div v-if="Math.random() > 0.5">
  Now you see me
</div>
<div v-else>
  Now you don't
</div>
1
2
3
4
5
6

v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别。

# v-else-if

v-else-if,顾名思义,充当 v-if 的“else-if 块”,并且可以连续使用:

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>
1
2
3
4
5
6
7
8
9
10
11
12

v-else 的用法类似,v-else-if 也必须紧跟在带 v-if 或者 v-else-if 的元素之后。

# v-show

另一个用于条件性展示元素的选项是 v-show 指令。用法大致一样:

<h1 v-show="ok">Hello!</h1>
1

不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS property display。 注意,v-show 不支持 <template> 元素,也不支持 v-else

# v-if vs v-show

v-if 是“真正”的条件渲染,因为它会确保在切换过程中,条件块内的事件监听器和子组件适当地被销毁和重建。 v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。 相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

# v-ifv-for 一起使用

提示

不推荐同时使用 v-ifv-for。请查阅风格指南以获取更多信息。

v-ifv-for 一起使用时,v-if 具有比 v-for 更高的优先级。请查阅列表渲染指南以获取详细信息。

# 循环遍历

# v-for 遍历数组

我们可以用 v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名

<ul id="array-rendering">
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>
1
2
3
4
5
Vue.createApp({
  data() {
    return {
      items: [{ message: 'Foo' }, { message: 'Bar' }]
    }
  }
}).mount('#array-rendering')
1
2
3
4
5
6
7

v-for 块中,我们可以访问所有父作用域的 property。v-for 还支持一个可选的第二个参数,即当前项的索引。

<ul id="array-with-index">
  <li v-for="(item, index) in items">
    {{ parentMessage }} - {{ index }} - {{ item.message }}
  </li>
</ul>
1
2
3
4
5
Vue.createApp({
  data() {
    return {
      parentMessage: 'Parent',
      items: [{ message: 'Foo' }, { message: 'Bar' }]
    }
  }
}).mount('#array-with-index')
1
2
3
4
5
6
7
8

你也可以用 of 替代 in 作为分隔符,因为它更接近 JavaScript 迭代器的语法:

<div v-for="item of items"></div>
1

# v-for 遍历对象

你也可以用 v-for 来遍历一个对象的 property。

<ul id="v-for-object" class="demo">
  <li v-for="value in myObject">
    {{ value }}
  </li>
</ul>
1
2
3
4
5
Vue.createApp({
  data() {
    return {
      myObject: {
        title: 'How to do lists in Vue',
        author: 'Jane Doe',
        publishedAt: '2016-04-10'
      }
    }
  }
}).mount('#v-for-object')
1
2
3
4
5
6
7
8
9
10
11

你也可以提供第二个的参数为 property 名称 (也就是键名 key):

<li v-for="(value, name) in myObject">
  {{ name }}: {{ value }}
</li>
1
2
3

还可以用第三个参数作为索引:

<li v-for="(value, name, index) in myObject">
  {{ index }}. {{ name }}: {{ value }}
</li>
1
2
3

提示

在遍历对象时,会按 Object.keys() 的结果遍历,但是不能保证它在不同 JavaScript 引擎下的结果都一致。

# 维护状态

当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。 这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。 为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute:

<div v-for="item in items" :key="item.id">
  <!-- content -->
</div>
1
2
3

建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。 因为它是 Vue 识别节点的一个通用机制,key 并不仅与 v-for 特别关联。后面我们将在指南中看到,它还具有其它用途。

提示

不要使用对象或数组之类的非基本类型值作为 v-for 的 key。请用字符串或数值类型的值。

更多 key attribute 的细节用法请移步至 key 的 API 文档

# 数组更新检测

# 变更方法

Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse() 你可以打开控制台,然后对前面例子的 items 数组尝试调用变更方法。比如 example1.items.push({ message: 'Baz' })

# 替换数组

变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法,例如 filter()concat()slice()。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组:

example1.items = example1.items.filter(item => item.message.match(/Foo/))
1

你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。

# 显示过滤/排序后的结果

有时,我们想要显示一个数组经过过滤或排序后的版本,而不实际变更或重置原始数据。在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组。 例如:

<li v-for="n in evenNumbers" :key="n">{{ n }}</li>
1
data() {
  return {
    numbers: [ 1, 2, 3, 4, 5 ]
  }
},
computed: {
  evenNumbers() {
    return this.numbers.filter(number => number % 2 === 0)
  }
}
1
2
3
4
5
6
7
8
9
10

在计算属性不适用的情况下 (例如,在嵌套 v-for 循环中) 你可以使用一个方法:

<ul v-for="numbers in sets">
  <li v-for="n in even(numbers)" :key="n">{{ n }}</li>
</ul>
1
2
3
data() {
  return {
    sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
  }
},
methods: {
  even(numbers) {
    return numbers.filter(number => number % 2 === 0)
  }
}
1
2
3
4
5
6
7
8
9
10

# v-for 中值的范围

v-for 也可以接受整数。在这种情况下,它会把模板重复对应次数。

<div id="range" class="demo">
  <span v-for="n in 10" :key="n">{{ n }} </span>
</div>
1
2
3
  • <template> 中使用 v-for 类似于 v-if,你也可以利用带有 v-for<template> 来循环渲染一段包含多个元素的内容。比如:
<ul>
  <template v-for="item in items" :key="item.msg">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>
1
2
3
4
5
6
  • v-forv-if 一同使用

TIP

注意我们推荐在同一元素上使用 v-ifv-for。更多细节可查阅风格指南

当它们处于同一节点,v-if 的优先级比 v-for 更高,这意味着 v-if 将没有权限访问 v-for 里的变量:

<!-- This will throw an error because property "todo" is not defined on instance. -->

<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>
1
2
3
4
5

可以把 v-for 移动到 <template> 标签中来修正:

<template v-for="todo in todos" :key="todo.name">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>
1
2
3
4
5
  • 在组件上使用 v-for

这部分内容假定你已经了解组件相关知识。你也完全可以先跳过它,以后再回来查看。 在自定义组件上,你可以像在任何普通元素上一样使用 v-for

<my-component v-for="item in items" :key="item.id"></my-component>
1

然而,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。为了把迭代数据传递到组件里,我们要使用 props:

<my-component
  v-for="(item, index) in items"
  :item="item"
  :index="index"
  :key="item.id"
></my-component>
1
2
3
4
5
6

不自动将 item 注入到组件里的原因是,这会使得组件与 v-for 的运作紧密耦合。明确组件数据的来源能够使组件在其他场合重复使用。 下面是一个简单的 todo 列表的完整例子:

<div id="todo-list-example">
  <form v-on:submit.prevent="addNewTodo">
    <label for="new-todo">Add a todo</label>
    <input
      v-model="newTodoText"
      id="new-todo"
      placeholder="E.g. Feed the cat"
    />
    <button>Add</button>
  </form>
  <ul>
    <todo-item
      v-for="(todo, index) in todos"
      :key="todo.id"
      :title="todo.title"
      @remove="todos.splice(index, 1)"
    ></todo-item>
  </ul>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const app = Vue.createApp({
  data() {
    return {
      newTodoText: '',
      todos: [
        {
          id: 1,
          title: 'Do the dishes'
        },
        {
          id: 2,
          title: 'Take out the trash'
        },
        {
          id: 3,
          title: 'Mow the lawn'
        }
      ],
      nextTodoId: 4
    }
  },
  methods: {
    addNewTodo() {
      this.todos.push({
        id: this.nextTodoId++,
        title: this.newTodoText
      })
      this.newTodoText = ''
    }
  }
})

app.component('todo-item', {
  template: `
    <li>
      {{ title }}
      <button @click="$emit('remove')">Remove</button>
    </li>
  `,
  props: ['title'],
  emits: ['remove']
})

app.mount('#todo-list-example')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 表单

# 基础用法

你可以用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理。

提示

v-model 会忽略所有表单元素的 valuecheckedselected attribute 的初始值而总是将当前活动实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

提示

对于需要使用输入法 (opens new window) (如中文、日文、韩文等) 的语言,你会发现 v-model 不会在输入法组织文字过程中得到更新。如果你也想响应这些更新,请使用 input 事件监听器和 value 绑定,而不是使用 v-model

# 文本 (Text)

<input v-model="message" placeholder="edit me" />
<p>Message is: {{ message }}</p>
1
2

# 多行文本 (Textarea)

<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br />
<textarea v-model="message" placeholder="add multiple lines"></textarea>
1
2
3
4

在文本区域插值不起作用,应该使用 v-model 来代替。

<!-- bad -->
<textarea>{{ text }}</textarea>

<!-- good -->
<textarea v-model="text"></textarea>
1
2
3
4
5

# 复选框 (Checkbox)

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

<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>
1
2

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

<div id="v-model-multiple-checkboxes">
  <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>
1
2
3
4
5
6
7
8
9
10
Vue.createApp({
  data() {
    return {
      checkedNames: []
    }
  }
}).mount('#v-model-multiple-checkboxes')
1
2
3
4
5
6
7

# 单选框 (Radio)

<div id="v-model-radiobutton">
  <input type="radio" id="one" value="One" v-model="picked" />
  <label for="one">One</label>
  <br />
  <input type="radio" id="two" value="Two" v-model="picked" />
  <label for="two">Two</label>
  <br />
  <span>Picked: {{ picked }}</span>
</div>
1
2
3
4
5
6
7
8
9
Vue.createApp({
  data() {
    return {
      picked: ''
    }
  }
}).mount('#v-model-radiobutton')
1
2
3
4
5
6
7

# 选择框 (Select)

单选时:

<div id="v-model-select" class="demo">
  <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>
1
2
3
4
5
6
7
8
9
Vue.createApp({
  data() {
    return {
      selected: ''
    }
  }
}).mount('#v-model-select')
1
2
3
4
5
6
7

注意

如果 v-model 表达式的初始值未能匹配任何选项,<select> 元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。

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

<select v-model="selected" multiple>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>
<br />
<span>Selected: {{ selected }}</span>
1
2
3
4
5
6
7

v-for 渲染的动态选项:

<div id="v-model-select-dynamic" class="demo">
  <select v-model="selected">
    <option v-for="option in options" :value="option.value">
      {{ option.text }}
    </option>
  </select>
  <span>Selected: {{ selected }}</span>
</div>
1
2
3
4
5
6
7
8
Vue.createApp({
  data() {
    return {
      selected: 'A',
      options: [
        { text: 'One', value: 'A' },
        { text: 'Two', value: 'B' },
        { text: 'Three', value: 'C' }
      ]
    }
  }
}).mount('#v-model-select-dynamic')
1
2
3
4
5
6
7
8
9
10
11
12

# 值绑定

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

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

<!-- `toggle` 为 true 或 false -->
<input type="checkbox" v-model="toggle" />

<!-- 当选中第一个选项时,`selected` 为字符串 "abc" -->
<select v-model="selected">
  <option value="abc">ABC</option>
</select>
1
2
3
4
5
6
7
8
9
10

但是有时我们可能想把值绑定到当前活动实例的一个动态 property 上,这时可以用 v-bind 实现,此外,使用 v-bind 可以将输入值绑定到非字符串。

# 复选框 (Checkbox)

<input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />
1
// when checked:
vm.toggle === 'yes'
// when unchecked:
vm.toggle === 'no'
1
2
3
4

提示

这里的 true-valuefalse-value attribute 并不会影响输入控件的 value attribute,因为浏览器在提交表单时并不会包含未被选中的复选框。如果要确保表单中这两个值中的一个能够被提交,(即“yes”或“no”),请换用单选按钮。

# 单选框 (Radio)

<input type="radio" v-model="pick" v-bind:value="a" />
1
// 当选中时
vm.pick === vm.a
1
2

# 选择框选项 (Select Options)

<select v-model="selected">
  <!-- 内联对象字面量 -->
  <option :value="{ number: 123 }">123</option>
</select>
1
2
3
4
// 当被选中时
typeof vm.selected // => 'object'
vm.selected.number // => 123
1
2
3

# 修饰符

.lazy 在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组织文字时)。你可以添加 lazy 修饰符,从而转为在 change 事件_之后_进行同步:

<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" />
1
2

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

<input v-model.number="age" type="number" />
1

这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。 .trim 如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

<input v-model.trim="msg" />
1