Vue-基础-前端

本文最后更新于:2021年3月24日 下午

信息

Vue (读音 /vjuː/) 是一套用于构建用户界面的渐进式框架
与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用
Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合

  • 渐进式
    指你可以让已有的项目,一点一点的用来Vue取代实现,不用担心Vue框架的排它性
    一些框架可能会强制你使用它的一些东西,使得这个框架与其它框架难以集成到一起
  • 组件化应用
    组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用

安装

Vue 不支持 IE8 及以下版本,因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性

直接用 <script>CDN引入

对于制作原型或学习,你可以这样使用最新版本

1
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

对于生产环境,我们推荐链接到一个明确的版本号和构建文件,以避免新版本造成的不可预期的破坏:

1
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>

NPM

在用 Vue 构建大型应用时推荐使用 NPM 安装。NPM 能很好地和webpack打包器配合使用

1
npm install vue

对不同构建版本的解释

NPM 包的 dist/ 目录会找到很多不同的 Vue.js 构建版本

UMD CommonJS ES Module
(基于构建工具使用)
ES Module
(直接用于浏览器)
完整版 vue.js vue.common.js vue.esm.js vue.esm.browser.js
只包含运行时版 vue.runtime.js vue.runtime.common.js vue.runtime.esm.js
完整版 (生产环境) vue.min.js vue.esm.browser.min.js
只包含运行时版 (生产环境) vue.runtime.min.js
  • 完整版:同时包含编译器和运行时的版本。
  • 编译器:用来将模板字符串编译成为 JavaScript 渲染函数的代码。
  • 运行时:用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码。基本上就是除去编译器的其它一切。

版本类型说明

  • UMD:UMD 版本可以通过 <script> 标签直接用在浏览器中
  • CommonJS:CommonJS 版本用来配合老的打包工具webpack 1
  • ES Module:为打包工具提供的 ESM
    • 为诸如 webpack 2 提供的现代打包工具
      ESM 格式被设计为可以被静态分析,所以打包工具可以利用这一点来进行“tree-shaking”并将用不到的代码排除出最终的包。
      为这些打包工具提供的默认文件 (pkg.module) 是只有运行时的 ES Module 构建 (vue.runtime.esm.js)
    • 为浏览器提供的 ESM (2.6+)
      用于在现代浏览器中通过 <script type="module"> 直接导入

浏览器调试插件Vue Devtools

Github: https://github.com/vuejs/vue-devtools#vue-devtools
寻找对应自己浏览器版本的调试插件安装即可

Hello World

创建一个HTML文件,在文件中引入Vue的CDN来使用Vue

1
2
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

HTML中设置一个标签用于显示数据

1
2
3
<div id="app">
{{message}}
</div>

编写模板代码

1
2
3
4
5
6
7
8
<script type="text/javascript">
var app = new Vue({
el:'#app',
data:{
message:"Hello World"
}
})
</script>

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统

这样就已经成功创建了第一个 Vue 应用!看起来这跟渲染一个字符串模板非常类似,但是 Vue 在背后做了大量工作
现在数据和 DOM 已经被建立了关联,所有东西都是响应式的。

要怎么确认呢?打开你的浏览器的控制台 (就在这个页面打开),并修改 app.message 的值

你将看到上例相应地更新

注意我们不再和 HTML 直接交互了
一个 Vue 应用会将其挂载到一个 DOM 元素上 (对于这个例子是 #app) 然后对其进行完全控制。那个 HTML 是我们的入口,但其余都会发生在新创建的 Vue 实例内部

其它的绑定

除了文本插值,我们还可以像这样来绑定元素 attribute

1
2
3
4
5
<div id="app-2">
<span v-bind:title="message">
鼠标悬停几秒钟查看此处动态绑定的提示信息!
</span>
</div>
1
2
3
4
5
6
7
8
<script type="text/javascript">
var app2 = new Vue({
el: '#app-2',
data: {
message: '页面加载于 ' + new Date().toLocaleString()
}
})
</script>

这里我们遇到了一点新东西。你看到的 v-bind attribute 被称为指令。指令带有前缀 v-,以表示它们是 Vue 提供的特殊 attribute。它们会在渲染的 DOM 上应用特殊的响应式行为
在这里,该指令的意思是:“将这个元素节点的 title attributeVue 实例的 message property 保持一致”

类似的,这个内容也是响应式的

条件

控制切换一个元素是否显示

1
2
3
<div id="app-3">
<p v-if="seen">现在你看到我了</p>
</div>
1
2
3
4
5
6
7
8
<script type="text/javascript">
var app3 = new Vue({
el: '#app-3',
data: {
seen: true
}
})
</script>


在控制台输入 app3.seen = false,你会发现之前显示的消息消失了。

这个例子演示了我们不仅可以把数据绑定到 DOM 文本或 attribute,还可以绑定到 DOM 结构

循环

v-for 指令可以绑定数组的数据来渲染一个项目列表

1
2
3
4
5
6
7
<div id="app-4">
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
<script type="text/javascript">
var app4 = new Vue({
el: '#app-4',
data: {
todos: [
{ text: '学习 JavaScript' },
{ text: '学习 Vue' },
{ text: '整个牛项目' }
]
}
})
</script>

输入

事件监听器

可以用 v-on 指令添加一个事件监听器,通过它调用在 Vue 实例中定义的方法

1
2
3
4
<div id="app-5">
<p>{{ message }}</p>
<button v-on:click="reverseMessage">反转消息</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
<script type="text/javascript">
var app5 = new Vue({
el: '#app-5',
data: {
message: 'Hello Vue.js!'
},
methods: {
reverseMessage: function () {
this.message = this.message.split('').reverse().join('')
}
}
})
</script>


注意在 reverseMessage 方法中,我们更新了应用的状态,但没有触碰 DOM
所有的 DOM 操作都由 Vue 来处理,你编写的代码只需要关注逻辑层面即可

双向绑定

Vue 提供了 v-model 指令,它能轻松实现表单输入和应用状态之间的双向绑定

1
2
3
4
<div id="app-6">
<p>{{ message }}</p>
<input v-model="message">
</div>
1
2
3
4
5
6
7
8
<script type="text/javascript">
var app6 = new Vue({
el: '#app-6',
data: {
message: 'Hello Vue!'
}
})
</script>

组件

组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用

Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。在 Vue 中注册组件很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="app-7">
<ol>
<!--
现在我们为每个 todo-item 提供 todo 对象
todo 对象是变量,即其内容可以是动态的。
我们也需要为每个组件提供一个“key”,稍后再作详细解释。
-->
<!-- 使用 v-bind 指令将待办项传到循环输出的每个组件中 -->
<todo-item
v-for="item in groceryList"
v-bind:todo="item"
v-bind:key="item.id"
></todo-item>
</ol>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script type="text/javascript">
// 定义名为 todo-item 的新组件
Vue.component('todo-item', {
// todo-item 组件接受一个"prop",类似于一个自定义 attribute。
// 这个 prop 名为 todo。
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})

var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ id: 0, text: '蔬菜' },
{ id: 1, text: '奶酪' },
{ id: 2, text: '随便其它什么人吃的东西' }
]
}
})
</script>

附带的观察整个流程(并非是运行流程),便于理解

  1. <script>初始化
    1. 声明自定义的组件<todo-item>
    2. 初始化数据 groceryList
  2. HTML里的<todo-item>
    1. v-for遍历groceryList中拿到数据,命名为item
    2. ``v-bind:todo=”item”相当于 组件.todo = item
    3. todo-item根据定义的template计算出内容回填

尽管这只是一个刻意设计的例子,但是我们已经设法将应用分割成了两个更小的单元。
子单元通过prop接口与父单元进行了良好的解耦
现在可以进一步改进 <todo-item> 组件,提供更为复杂的模板和逻辑,而不会影响到父单元

Vue实例

每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的

1
2
3
var vm = new Vue({
// 选项
})

虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例
当创建一个 Vue 实例时,你可以传入一个选项对象。这个篇章主要描述的就是如何使用这些选项来创建你想要的行为。作为参考,你也可以在 API 文档 中浏览完整的选项列表

一个 Vue 应用由一个通过 new Vue 创建的根 Vue 实例,以及可选的嵌套的、可复用的组件树组成

举个例子,一个 todo 应用的组件树可以是这样的:

1
2
3
4
5
6
7
8
根实例
└─ TodoList
├─ TodoItem
│ ├─ TodoButtonDelete
│ └─ TodoButtonEdit
└─ TodoListFooter
├─ TodosButtonClear
└─ TodoListStatistics

所有的 Vue 组件都是 Vue 实例,并且接受相同的选项对象 (一些根实例特有的选项除外)

数据与方法

当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中
当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 我们的数据对象
var data = { a: 1 }

// 该对象被加入到一个 Vue 实例中
var vm = new Vue({
data: data
})

// 获得这个实例上的 property
// 返回源数据中对应的字段
vm.a == data.a // => true

// 设置 property 也会影响到原始数据
vm.a = 2
data.a // => 2

// ……反之亦然
data.a = 3
vm.a // => 3

当这些数据改变时,视图会进行重渲染。值得注意的是只有当实例被创建时就已经存在于 data 中的 property 才是响应式的

也就是说如果你在创建以后再去添加一个新的 property,比如:

1
vm.b = 'hi'

那么对 b 的改动将不会触发任何视图的更新

如果你知道你会在晚些时候需要一个 property,但是一开始它为空或不存在,那么你需要设置一些初始值,以确保它是响应式的

1
2
3
4
5
6
7
data: {
newTodoText: '',
visitCount: 0,
hideCompletedTodos: false,
todos: [],
error: null
}

冻结

Object.freeze()会阻止修改现有的 property,也意味着响应系统无法再追踪变化

1
2
3
4
5
<div id="app">
<p>{{ foo }}</p>
<!-- 这里的 `foo` 不会更新! -->
<button v-on:click="foo = 'baz'">Change it</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
<script type="text/javascript">
var obj = {
foo: 'bar'
}

Object.freeze(obj)

new Vue({
el: '#app',
data: obj
})
</script>

前缀$

除了数据 propertyVue 实例还暴露了一些有用的实例 property 与方法。它们都有前缀 $,以便与用户定义的 property 区分开来

1
2
3
4
5
6
7
8
9
10
11
12
13
var data = { a: 1 }
var vm = new Vue({
el: '#example',
data: data
})

vm.$data === data // => true
vm.$el === document.getElementById('example') // => true

// $watch 是一个实例方法
vm.$watch('a', function (newValue, oldValue) {
// 这个回调将在 `vm.a` 改变后调用
})

可以在 API 中查阅到完整的实例 property 和方法的列表

生命周期

生命周期钩子

每个Vue实例在被创建时都要经过一系列的初始化过程

例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM

同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会

比如 created 钩子可以用来在一个实例被创建之后执行代码:

1
2
3
4
5
6
7
8
9
10
new Vue({
data: {
a: 1
},
created: function () {
// `this` 指向 vm 实例
console.log('a is: ' + this.a)
}
})
// => "a is: 1"

也有一些其它的钩子,在实例生命周期的不同阶段被调用。如 mountedupdateddestroyed
生命周期钩子的 this 上下文指向调用它的 Vue 实例

注意:不要在选项 property 或回调上使用箭头函数

比如
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 之类的错误

生命周期图例


图例上说明了可以使用的钩子函数(红框红字)与生命周期过程
以后遇到问题可以作为参考

模板语法

Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据
所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析

在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少

插值

顾名思义,就是往HTML中插入一些内容,以填充页面

普通文本 - “Mustache”语法 (双大括号)

向HTML中插入变量的值

数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值

  • 无法对元素属性使用
  • 插入的HTML内容不会被当做元素,只会当做普通字符串
1
<span>Message: {{ msg }}</span>

Mustache 标签将会被替代为对应数据对象上 msg property 的值
无论何时,绑定的数据对象上 msg property 发生了改变,插值处的内容都会更新

插值固定 - v-once 指令

用于影响插值,在此指令范围内的文本插值后不会因其原本变量值改变而改变

1
<span v-once>这个将不会改变: {{ msg }}</span>

HTML内容 - v-html 指令

用于插入HTML内容

双大括号会将数据解释为普通文本,要插入HTML内容,需要用这个v-html 指令

1
2
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

这个 span 的内容将会被替换成为 propertyrawHtml,直接作为 HTML(忽略解析 property 值中的数据绑定)

注意,你不能使用 v-html 来复合局部模板,因为 Vue 不是基于字符串的模板引擎。反之,对于用户界面 (UI),组件更适合作为可重用和可组合的基本单位

你的站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值

元素属性插值 - v-bind 指令

用于绑定内容与目标元素的属性

Mustache 语法不能作用在 HTML attribute 上,遇到这种情况应该使用 v-bind 指令

1
<div v-bind:id="dynamicId"></div>

对于布尔 attribute (它们只要存在就意味着值为 true),v-bind 工作起来略有不同

1
<button v-bind:disabled="isButtonDisabled">Button</button>

如果isButtonDisabled 的值是 nullundefinedfalse,则 disabled attribute 甚至不会被包含在渲染出来的 <button> 元素中

JavaScript 表达式

实际上,对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持

1
2
3
4
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>

这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。有个限制就是,每个绑定都只能包含单个表达式

下面的例子都不会生效

  • 是语句,不是表达式 {{ var a = 1 }}
  • 流控制也不会生效,请使用三元表达式 {{ if (ok) { return message } }}

模板表达式都被放在沙盒中,只能访问一些特定的全局变量,如 MathDate
你不应该在模板表达式中访问用户定义的全局变量

指令

指令 (Directives) 是带有 v- 前缀的特殊 attribute
指令 attribute 的值预期是单个 JavaScript 表达式 (v-for 是例外情况,稍后我们再讨论)
指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM

参数

一些指令能够接收一个“参数”,在指令名称之后以冒号表示

例: v-bind 指令可以用于响应式地更新 HTML attribute

1
<a v-bind:href="url">...</a>

在这里 href 是参数,告知 v-bind 指令将该元素的 href attribute 与表达式 url 的值绑定

例: v-on 指令,它用于监听 `DOM 事件

1
<a v-on:click="doSomething">...</a>

在这里参数是监听的事件名

动态参数

可以用方括号括起来的 JavaScript 表达式作为一个指令的参数

例:

1
2
3
4
<!--
注意,参数表达式的写法存在一些约束,如之后的“对动态参数表达式的约束”章节所述。
-->
<a v-bind:[attributeName]="url"> ... </a>

如果你的 Vue 实例有一个 data property attributeName,其值为 “href”,那么这个绑定将等价于 v-bind:href

这里的 attributeName 会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用

同样地,你可以使用动态参数为一个动态的事件名绑定处理函数:

例:

1
<a v-on:[eventName]="doSomething"> ... </a>

eventName 的值为 “focus” 时,v-on:[eventName] 将等价于 v-on:focus

约束

对动态参数的值的约束
  • null
    动态参数预期会求出一个字符串,异常情况下值为 null
    这个特殊的 null 值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。
对动态参数表达式的约束
  • 特殊符号约束
    动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的

    例:

    1
    2
    <!-- 这会触发一个编译警告 -->
    <a v-bind:['foo' + bar]="value"> ... </a>

    变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂表达式

  • 大小写约束
    DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写

    例:

    1
    2
    3
    4
    5
    <!--
    在 DOM 中使用模板时这段代码会被转换为 `v-bind:[someattr]`。
    除非在实例中有一个名为“someattr”的 property,否则代码不会工作。
    -->
    <a v-bind:[someAttr]="value"> ... </a>

    修饰符

    修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。

    例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()

    1
    <form v-on:submit.prevent="onSubmit">...</form>

    缩写

    v- 前缀作为一种视觉提示,用来识别模板中 Vue 特定的 attribute
    Vuev-bindv-on 这两个最常用的指令,提供了特定简写

v-bind 缩写

1
2
3
4
5
6
7
8
<!-- 完整语法 -->
<a v-bind:href="url">...</a>

<!-- 缩写 -->
<a :href="url">...</a>

<!-- 动态参数的缩写 (2.6.0+) -->
<a :[key]="url"> ... </a>

v-on 缩写

1
2
3
4
5
6
7
8
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>

<!-- 缩写 -->
<a @click="doSomething">...</a>

<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>

它们看起来可能与普通的 HTML 略有不同,但 :@ 对于 attribute 名来说都是合法字符,在所有支持 Vue 的浏览器都能被正确地解析
它们不会出现在最终渲染的标记中

计算属性和侦听器

计算属性

模板表达式 与 计算属性

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的

在模板中放入太多的逻辑会让模板过重且难以维护

1
2
3
<div id="example">
{{ message.split('').reverse().join('') }}
</div>

在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串
当你想要在模板中多包含此处的翻转字符串时,就会更加难以处理

对于任何复杂逻辑,你都应当使用计算属性

计算属性

1
2
3
4
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script type="text/javascript">
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 声明了一个计算属性 reversedMessage
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})
</script>

这里声明了一个 “计算反转的vm.message字符串的函数” 传入到 计算属性 reversedMessage
提供的函数将用作 property vm.reversedMessagegetter 函数
计算属性 reversedMessage 的结果是响应式的

reversedMessage 的值始终取决于 vm.message 的值

你可以像绑定普通 property 一样在模板中绑定计算属性
Vue 知道 vm.reversedMessage 依赖于 vm.message,因此当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新
而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解

计算属性的 setter

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

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

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

计算属性缓存 与 方法

你可能会想到:通过 在表达式中调用方法 来达到和 计算属性 同样的效果

1
<p>Reversed message: "{{ reversedMessage() }}"</p>
1
2
3
4
5
6
// 在组件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}

两种方式的最终结果确实是完全相同的
不同的是 计算属性 是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值

只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而会再次执行函数

可能造成的问题
下面的 计算属性 将不再更新,因为 Date.now() 不是响应式依赖

1
2
3
4
5
computed: {
now: function () {
return Date.now()
}
}
计算属性 方法
更新结果 依赖更新时更新
否则从缓存中得到上次的计算结果
每次运行都更新
资源消耗
速度 较快 较慢

计算属性 vs 侦听属性

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性(watch)
但通常计算属性 能够取代 命令式的 watch回调,并且让代码更优美

例:姓更变 或 名更变 时 自动计算全名

1
<div id="demo">{{ fullName }}</div>

此处用watch实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script type="text/javascript">
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
// 因为要监听两个变量,所以定义了两个
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
</script>

此处用 计算属性实现

1
2
3
4
5
6
7
8
9
10
11
12
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})

对比而言,计算属性 的代码就优美很多

侦听器

Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

例:异步Ajax请求

1
2
3
4
5
6
7
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>

使用 watch 选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的

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
45
<!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 -->
<!-- 提供这些功能以保持精简。这也可以让你自由选择自己更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},
created: function () {
// `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
// 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
// AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
// `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
// 请参考:https://lodash.com/docs#debounce
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer: function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
}
}
})
</script>

除了 watch 选项之外,还可以使用命令式的 vm.$watch API

Class 与 Style绑定

操作元素的 class 列表和内联样式是数据绑定的一个常见需求。它们都是 attribute,所以可以用 v-bind 处理它们
在将 v-bind 用于 classstyle 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组

绑定 HTML Class

对象语法

多字段设置

可以传给 v-bind:class 多个字段,以动态地切换 class

1
2
3
4
<div 
class="static"
v-bind:class="{ active: isActive }"
></div>

v-bind:class 指令也可以与普通的 class attribute 共存,最终得到的结果会是它们拼接在一起的样子

多字段来动态切换Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div
id="example"
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>

<script type="text/javascript">
var vm = new Vue({
el: '#example',
data: {
isActive: true,
hasError: false
}
})
</script>

会得到这样的渲染结果 static activate

类似的,这样设置的样式是响应式的,如果修改isActivehasError设置的内容也会相应的做出改变

单对象

与上面的多个字段类似,可以在一个对象中设置多个属性(作为字段),来进行class的切换

单个对象来动态切换class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div
id="example"
class="static"
v-bind:class="classObject"
></div>

<script type="text/javascript">
var vm = new Vue({
el: '#example',
data: {
classObject: {
active: true,
'text-danger': false
}
}
})
</script>

会得到和上面的例子一样的的结果

数组语法

可以把一个数组传给 v-bind:class,以应用一个 class 列表

数组来动态切换class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div 
id="example"
v-bind:class="[activeClass, errorClass]"
></div>

<script type="text/javascript">
var vm = new Vue({
el: '#example',
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
})
</script>

最终渲染结果为class="active text-danger"

你可以在数组中填入表达式,但这会造成内容的不美观,不建议那样做

绑定内联样式 style

v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象
CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名

多字段设置style

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div 
id="example"
v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }">Hello
</div>

<script type="text/javascript">
var vm = new Vue({
el: '#example',
data: {
activeColor: 'red',
fontSize: 30
}
})
</script>

最终得到的style="color: red; font-size: 30px;"

对象设置Style

类似的,v-bind:style可以在一个或多个对象中设置属性(作为字段),来进行style的设置

单个对象设置 style

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div 
id="example"
v-bind:style="styleObject">Hello
</div>

<script type="text/javascript">
var vm = new Vue({
el: '#example',
data: {
styleObject: {
color: 'red',
fontSize: '30px'
}
}
})
</script>

得到的结果与上个例子一致: style="color: red; font-size: 30px;"

对于希望设置多个对象到元素上时,需要用数组括起来

多个对象设置 style

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div 
id="example"
v-bind:style="[colorStyleObject, fontStyleObject]">Hello
</div>

<script type="text/javascript">
var vm = new Vue({
el: '#example',
data: {
colorStyleObject: {
color: 'red'
},
fontStyleObject:{
fontSize: '30px'
}
}
})
</script>

得到的结果与上个例子一致: style="color: red; font-size: 30px;"

自动添加前缀

v-bind:style 使用需要添加浏览器引擎前缀的 CSS property 时,如 transformVue.js 会自动侦测并添加相应的前缀

多重值

可以为 style 绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值,这样写只会渲染数组中最后一个被浏览器支持的值

包含多个值的 preoperty 数组

1
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

如果此处浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex

条件渲染

v-if 与 v-else

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

v-else 指令来表示 v-if 的 “else 块”
v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别

v-ifv-else的基本使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="example">
<h1 v-if="is_awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
</div>

<script type="text/javascript">
var vm = new Vue({
el: '#example',
data: {
is_awesome: true
}
})
</script>


依旧,这个渲染是响应式的,而且在状态发生改变之前,页面上不会有多余的元素(并不是hide,而是完全没有)

v-if 与 v-for 一起使用

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

v-else-if

v-else-if,顾名思义,其作用就相当于马上在else后接一个if判断
类似于 v-elsev-else-if 也必须紧跟在带 v-if 或者 v-else-if 的元素之后

v-else-if的使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="example">
<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>
</div>

<script type="text/javascript">
var vm = new Vue({
el: '#example',
data: {
type: 'A'
}
})
</script>

用 key 管理可复用的元素

Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染 这么做除了使 Vue 变得非常快之外,还有其它一些现象

尽可能复用:用户在不同的登录方式之间切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="example">
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
</div>

<script type="text/javascript">
var vm = new Vue({
el: '#example',
data: {
loginType: 'username'
}
})
</script>


切换 loginType 将不会清除用户已经输入的内容
两个模板都使用了相同的元素<input>。因为vue总是希望尽可能复用,这个元素不会被替换掉(此处仅替换了它的 placeholder属性)
这会造成一些问题,一般切换输入类型都需要将已输入内容清空,而这里没清
如果不希望vue最大程度复用,那么可以为元素添加key属性
需要注意,key属性需要一个唯一值

独立的key:用户在不同的登录方式之间切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  <div id="example">
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
</div>

<script type="text/javascript">
var vm = new Vue({
el: '#example',
data: {
loginType: 'username'
}
})
</script>


在设置了key后,模板内容会被重新渲染后才放到页面上。所以输入的内容消失了

v-show

另一个用于根据条件展示元素的选项是 v-show 指令
用法与 v-if 类似,都是表达式返回 truthy 值的时候被渲染
不同的是带有 v-show 的元素始终会被渲染并保留在 DOM
v-show 只是简单地切换元素的 CSS property display

注意,v-show 不支持 <template> 元素,也不支持 v-else

v-show的使用

1
2
3
4
5
6
7
8
9
10
11
12
<div id="example">
<h1 v-show="is_awesome">Vue is awesome!</h1>
</div>

<script type="text/javascript">
var vm = new Vue({
el: '#example',
data: {
is_awesome: true
}
})
</script>

v-if 对比 v-show

v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。

v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。

一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销 因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好

列表渲染

v-for 对应数组

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

v-for对应数组到元素
v-for 还支持一个可选的第二个参数,即当前项的索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<ul id="example">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>

<script type="text/javascript">
var example2 = new Vue({
el: '#example',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
</script>

v-for 对应对象

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<ul id="v-for-object" class="demo">
<li v-for="(value, name, index) in object">
{{index}} : {{ name }} : {{ value }}
</li>
</ul>

<script type="text/javascript">
var example3 = new Vue({
el: '#v-for-object',
data: {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
})
</script>

维护状态

当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略
如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染

这个默认的模式是高效的,但是只适用于 不依赖子组件状态 或 临时 DOM 状态 (例如:表单输入值) 的列表渲染输出

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute:

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

建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升

因为它是 Vue 识别节点的一个通用机制,key 并不仅与 v-for 特别关联。后面我们将在指南中看到,它还具有其它用途

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

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

数组更新检测

变更方法

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

  • push()、pop()
  • shift()、unshift()
  • splice()
  • sort()
  • reverse()

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

替换数组

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

1
2
3
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})

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

注意事项
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化深入响应式原理 中有相关的讨论

显示过滤/排序后的结果

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

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<ul id="v-for-after-filter" >
<li v-for="n in evenNumbers">
{{ n }}
</li>
</ul>

<script type="text/javascript">
var example2 = new Vue({
el: '#v-for-after-filter',
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
})
</script>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="for-for">
<ul v-for="set in sets">
<li v-for="n in even(set)">{{ n }}</li>
</ul>
</div>

<script type="text/javascript">
var example2 = new Vue({
el:"#for-for",
data: {
sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
})

在 v-for 中使用值范围

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

1
2
3
<div>
<span v-for="n in 10">{{ n }} </span>
</div>

<template> 上使用 v-for

类似于 v-if,你也可以利用带有 v-for 的 <template> 来循环渲染一段包含多个元素的内容。比如:

1
2
3
4
5
6
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>

v-for 与 v-if 一同使用

注意:不推荐在同一元素上使用 v-if 和 v-for

当它们处于同一节点,v-for 的优先级比 v-if 更高
这意味着 v-if 将分别重复运行于每个 v-for 循环中

当你只想为部分项渲染节点时,这种优先级的机制会十分有用

例:只渲染未完成的 todo

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

事件处理

监听事件

可以用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="example-1">
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>

<script type="text/javascript">
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})
</script>

事件处理方法

许多事件处理逻辑会更为复杂,把 JavaScript 代码直接写在 v-on 指令中是不可行的
v-on 可以接收一个需要调用的方法名称

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="example-2">
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
</div>

<script type="text/javascript">
var example = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// 在 `methods` 对象中定义方法
methods: {
greet: function (event) {
// `this` 在方法里指向当前 Vue 实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
}
})
</script>

内联处理器中的方法

除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="example-3">
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
</div>

<script type="text/javascript">
var example = new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})
</script>

有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="example">
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
</div>

<script type="text/javascript">
var example = new Vue({
el: '#example',
methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) {
event.preventDefault()
}
alert(message)
}
}
</script>

事件修饰符

在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是非常常见的需求
尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节

为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive
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
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生
因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击

注意:不要把 .passive.prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive 会告诉浏览器你不想阻止事件的默认行为

按键修饰符

Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:

1
2
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">

你可以直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符

1
<input v-on:keyup.page-down="onPageDown">

在上述示例中,处理函数只会在 $event.key 等于 PageDown 时被调用

表单输入绑定

基础用法

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

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

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

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

注:对于需要使用输入法 (如中文、日文、韩文等) 的语言,你会发现 v-model 不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input 事件

文本

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

多行文本

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

在文本区域插值 (<textarea>{{text}}</textarea>) 并不会生效,应用 v-model 来代替

复选框

对于单个复选框,可以绑定到布尔值中

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

对于多个复选框,可以绑定到同一个数组中

1
2
3
4
5
6
7
8
<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>
1
2
3
4
5
6
new Vue({
el: '...',
data: {
checkedNames: []
}
})

单选按钮

1
2
3
4
5
6
7
8
9
<div id="example-4">
<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
new Vue({
el: '#example-4',
data: {
picked: ''
}
})

选择框

单选时

1
2
3
4
5
6
7
8
9
<div id="example-5">
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
1
2
3
4
5
6
new Vue({
el: '...',
data: {
selected: ''
}
})

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

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

1
2
3
4
5
6
7
8
9
<div id="example-6">
<select v-model="selected" multiple style="width: 50px;">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected: {{ selected }}</span>
</div>
1
2
3
4
5
6
new Vue({
el: '#example-6',
data: {
selected: []
}
})

值绑定

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

1
2
3
4
5
6
7
8
9
10
<!-- 当选中时,`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>

但是有时我们可能想把值绑定到 Vue 实例的一个动态 property 上,这时可以用 v-bind 实现,并且这个 property 的值可以不是字符串

1
2
3
<input type="checkbox" v-model="toggle"
true-value="yes" false-value="no"
>
1
2
3
4
// 当选中时
vm.toggle === 'yes'
// 当没有选中时
vm.toggle === 'no'

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

单选按钮绑定

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

选择框选项绑定

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

修饰符

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

    1
    2
    <!-- 在“change”时而非“input”时更新 -->
    <input v-model.lazy="msg">
  • .number
    如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

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

    这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值

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

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

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!