一、vue是什么
vue是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,vue被设计为可以自底向上逐层应用。vue的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持库结合使用时,vue也完全能够为复杂的单页应用提供驱动。
二、组件化应用构建
组件系统是vue的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树:
在vue里,一个组件本质上是一个拥有预定义选项的一个vue实例。在vue中注册组件很简单:
// 定义名为todo-item 的新组件
Vue.component('todo-item', {
template: '<li>这是个待办项</li>'
})
现在你可以用它构建另一个组件模板:
<ol>
<!-- 创建一个 todo-item 组件的实例-->
<todo-item></todo-item>
</ol>
三、实例生命周期钩子
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
生命周期钩子的 this 上下文指向调用它的 Vue 实例。
不要在选项属性或回调上使用箭头函数,比如 created: () => console.log(this.a)
或 vm.$watch('a', newValue => this.myMethod())
。因为箭头函数是和父级上下文绑定在一起的,this
不会是如你所预期的 Vue 实例。
生命周期图示
生命周期钩子的函数有以下8个:
beforeCreate (vue初始化的时候执行)
created (初始化完成之后执行)
beforeMount (页面还没渲染时执行)
mounted (页面已经渲染完成后执行)
beforeDestroy (当组件即将被消除时执行)
destroyed (当实例被销毁之后执行)
beforeUpdate (当数据发生改变还没渲染之前执行)
updated (当数据发生改变渲染后执行)
四、模板语法
1、插值
文本
数据绑定最常见的形式就是使用“Mustache”语法(双大括号)的文本插值:{{}}
无论何时,绑定的数据对象上的属性发生了改变,插值处的内容都会更新。
通过使用v-once指令
,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新,但请留心这个会影响到该节点上的其他数据绑定;
<span v-once>这个将不会改变: {{}msg}</span>
原始HTML
双大括号会将数据解释为普通文本,而非HTML代码。为了输出真正的HTML,你需要使用v-html指令:
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive:
<span v-html="rawHtml"></span>
</p>
使用javascript表达式
vue都提供了完全的javascript表达式支持。
有个限制式,每个绑定只能包含单个表达式。
2、指令
指令是带有 v-
前缀的特殊特性。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式得作用于DOM。
缩写
v-bind
缩写
<!--完整写法-->
<a v-bind:href="url">……</a>
<!--缩写-->
<a :href="url">……</a>
v-on
缩写
<!--完整写法-->
<a v-on:click="doSomething">……</a>
<!--缩写-->
<a @click="doSomething">……</a>
五、计算属性和侦听器
对于任何复杂逻辑,都应当使用计算属性。
我们可以通过表达式中调用方法和使用计算属性computed,可以达到相同的效果,然而不同的是计算属性是基于它们的依赖进行缓存的。只在相关依赖发生改变时它们才会重新求值。
vue提供了一种更通用的方式来观察和响应vue实例上的数据变动:侦听属性。
<div id="app">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
<p>firstName: {{firstName}}</p>
<p>lastName: {{lastName}}</p>
<p>fullName: {{fullName}}</p>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello',
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
},
// 计算全名
fullName: {
get: function () {
return this.firstName + ' ' + this.lastName
},
set: function (newValue) {
var name = newValue.split(' ');
this.firstName = name[0];
this.lastName = name[name.length - 1]
}
}
}
})
</script>
页面显示如下:
在控制台修改fulName的内容,页面上的firstName和lastName的值也随之改变
六、条件渲染
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回truthy
值时被渲染。也可以用v-else
添加一个“else”块。
<div id="app">
<h1 v-if="awesome">Vue is awesome</h1>
<h1 v-else>OH no 😢</h1>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
awesome: true
}
})
</script>
这时页面显示如下:
当在控制台将awesome的值修改为false时页面显示如下:
在 <template> 元素上使用 v-if 条件渲染分组"
因为 v-if
是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 <template>
元素当做不可见的包裹元素,并在上面使用v-if
。最终的渲染结果将不包含 <template>
元素。
v-else
你可以使用v-else
指令来表示v-if
的‘else块’,v-else
元素必须紧跟在带v-if
或者 v-else-if
的元素的后面,否则它将不会被识别。
用key
管理可复用的元素
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:
<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>
那么在上面的代码中切换loginType
将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,<input>
不会被替换掉——仅仅是替换了它的placeholder
。
这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key 属性即可:
<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>
v-show
另一个用于根据条件展示元素的选项是v-show
指令。用法和v-if
一致。不同的是v-show
的元素始终会被渲染并保留在DOM中。v-show
只是简单的切换元素的css属性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-if 与 v-for一起使用
- 不推荐同时使用
v-if
和v-for
,当v-if
与v-for
一起使用时,v-for
具有比v-if
更高的优先级。
七、列表渲染
我们用 v-for
指令根据一组数组的选项列表进行渲染。v-for
指令需要使用item in items
形式的特殊语法,items
是源数据数组并且item
是数组元素迭代的别名。
在 v-for
块中,我们拥有对父作用域属性的完全访问权限。v-for
还支持一个可选的第二个参数 index
为当前项的索引。
<div id="app">
<ul>
<li v-for="(item, index) in items">
{{parentMessage}} - {{index}} - {{item.message}}
</li>
</ul>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
parentMessage: 'parent',
items: [
{message: 'Foo'},
{message: 'Bar'}
]
}
})
</script>
可以用of代替in作为分割符,因为它是最接近javascript迭代器的语法:
<li v-for="item of items">
{{item.message}}
</li>
key
当 Vue.js 用 v-for
正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。这个类似 Vue 1.x 的 track-by="$index"
。
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。理想的 key 值是每项都有的唯一 id。它的工作方式类似于一个属性,所以你需要用 v-bind 来绑定动态值 (在这里使用简写):
<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>
建议尽可能在使用 v-for 时提供 key,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
因为它是 Vue 识别节点的一个通用机制,key 并不与 v-for 特别关联。
- 不要使用对象或数组之类的非原始类型值作为 v-for 的 key。用字符串或数类型的值取而代之。
数组更新检测
变异方法
vue包含一组观察数组的变异方法,所以它们也将会触发视图更新。这些方法如下:
push() //可向数组的末尾添加一个或多个元素,并返回新的长度
pop() // 要删除并返回数组的最后一个元素
shift() // 用于把数组的第一个元素从其中删除
unshift() // 可向数组的开头添加一个或更多元素,并返回新的长度
splice() // 向/从数组中添加/删除项目,然后返回被删除的项目
sort() // 对数组元素进行排序
reverse() // 用于颠倒数组中元素的顺序
打开控制台,用前面的例子的items数组调用变异方法
替换数组
变异方法 (mutation method),顾名思义,会改变被这些方法调用的原始数组。相比之下,也有非变异 (non-mutating method) 方法,例如:filter(), concat() 和 slice() 。这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组:
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
注意事项
由于 JavaScript 的限制,Vue 不能检测以下变动的数组:
1、当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
2、当你修改数组的长度时,例如:vm.items.length = newLength
解决这个问题
解决第一类问题: 可以使用vm.$set
实例方法,该方法是全局方法 Vue.set
的一个别名
vm.$set(vm.items, indexOfItem, newValue)
解决第二类 问题:可以使用splice:
vm.items.splice(newLength)
八、事件处理
我们可以用 v-on
指令监听DOM事件,并在触发时运行以下js代码。然而许多事件处理逻辑会更为复杂,所以直接把js代码写在 v-on
指令中是不可行的。因此 v-on
还可以接收一个需要调用的方法名称。
<div id="example-2">
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
</div>
var example2 = 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)
}
}
}
})
// 也可以用 JavaScript 直接调用方法
example2.greet() // => 'Hello Vue.js!'
事件修饰符
在事件处理程序中调用event.preventDefault()
或 event.stopPropagation()
是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
为了解决这个问题,Vue.js 为 v-on
提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。
.stop
.prevent
.capture
.self
.once
.passive
不像其他只能对原生的dom事件起作用的修饰符,.once
修饰符还能被用到自定义的组件事件上。
vue还对应 addEventListener
中的 passive
选项提供了 .passive
修饰符。这个 .passive
修饰符尤其能够提升移动端的性能。
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
- 不要把
.passive
和.prevent
一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive 会告诉浏览器你不想阻止事件的默认行为。
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<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>
- 使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用
v-on:click.prevent.self
会阻止所有的点击,而v-on:click.self.prevent
只会阻止对元素自身的点击。
为什么在HTML中监听事件?
你可能注意到这种事件监听的方式违背了关注点分离这个长期以来的优良传统。但不必担心,因为所有的vue.js事件处理方法和表达式都严格绑定在当前视图的viewmodel上,它不会导致任何维护上的困难。实际上,使用v-on有几个好吃:
1、扫一眼HTML模板便能轻松定位在javascript代码里对应的方法。
2、因为你无须在js里手动绑定事件,你的viewmodel代码可以是非常纯粹的逻辑,和DOM完全解耦,更易于测试。
3、当一个viewmodel被销毁时,所有的事件处理器会自动被删除。你无须担心如何清理它们。
九、表单输入绑定
你可以用 v-model
指令在表单 <input>
、<textarea>
及 <select>
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model
本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model
会忽略所有表单元素的 value
、checked
、selected
特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data
选项中声明初始值。
v-model
在内部使用不同的属性为不同的输入元素并抛出不同的事件:
- text 和 textarea 元素使用
value
属性和input
事件; - checkbox 和 radio 使用
checked
属性和change
事件; - select 字段将
value
作为 prop 并将change
作为事件。
对于需要使用输入法 (如中文、日文、韩文等) 的语言,你会发现 v-model
不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input
事件。
我们也可以在组件上使用 v-model
修饰符
.lazy
在默认情况下,v-model
在每次 input
事件触发后将输入框的值与数据进行同步 (除了输入法组合文字时)。你可以添加 lazy
修饰符,从而转变为使用 change
事件进行同步:
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" >
.number
如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:
<input v-model.number="age" type="number">
.trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:
<input v-model.trim="msg">
十、组件基础
实例:
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script>
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">你单击了我{{count}}次。</button>'
});
var app = new Vue({
el: '#app'
})
</script>
- data必须是个函数
当我们定义这个 <button-counter> 组件时,你可能会发现它的 data 并不是像这样直接提供一个对象:
data: {
count: 0
}
取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
data: function () {
return {
count: 0
}
}
通过插槽分发内容
和HTML元素一样,我们经常需要向一个组件传递内容,像这样:
<alert-box>
Something bad happened.
</alert-box>
可能会渲染出这样的东西:
幸好,Vue自定义的<slot>元素让这变得非常简单:
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
如你所见,我们只要在需要的地方加入插槽就行了——就这么简单!
动态组件
有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里:
上述内容可以通过 Vue 的 <component> 元素加一个特殊的 is 特性来实现:
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>
在上述示例中,currentTabComponent
可以包括
- 已注册组件的名字,或
- 一个组件的选项对象
你可以在这里查阅并体验完整的代码,或在这个版本了解绑定组件选项对象,而不是已注册组件名的示例。
解析DOM模板时的注意事项
有些HTML元素,诸如<ul>、<ol>、<table>和<select>,对于哪些元素可以出现在其内部是严格限制的。而有些元素,诸如<li>、<tr>和<option>,只能出现在其他某些特定的元素内部。
is
特性给了我们一个变通的办法:
<table>
<tr is="blog-post-row"></tr>
</table>
需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:
- 字符串 (例如:
template: '...'
) - 单文件组件 (
.vue
) <script type="text/x-template">