Vue.js 不支持 IE8 及其以下版本,因为 Vue.js 使用了 IE8 不能实现的 ECMAScript 5 特性。
开发环境部署
可参考使用 vue+webpack。
基本用法
1
2
3
4
5
6
7
8
9
10
11
|
<div id="app">
{{message}}
<input v-model="message">
</div>
new Vue({
ele: '#app',
data: {
message: 'hello'
}
})
|
列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<div id="app">
<li v-for="todo in todos">{{todo.text}}</li>
</div>
new Vue({
ele: '#app',
data: {
todos: [{
text: '111'
}, {
text: '222'
}]
}
})
|
事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<div id="app">
<p>{{message}}</p>
<button v-on:click="reverseMessage">Reverse Message</button>
</div>
new Vue({
el: '#app',
data: {
message: 'hello vue'
},
methods: {
reverseMessage: () {
this.message = this.message.split('').reverse().join('')
}
}
})
|
综合使用
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
|
<div id="app">
<input v-model="newTodo" v-on:keyup.enter="addTodo">
<ul>
<li v-for="todo in todos">
<span>{{todo.text}}</span>
<button v-on:click="removeTodo($index)">X</button>
</li>
</ul>
</div>
new Vue({
el: '#app',
data: {
newTodo: '',
todos: [{
text: 'Add todos'
}]
},
methods: {
addTodo: () {
var text = this.newTodo.trim();
if(text) {
this.todos.push({text: text})
this.newTodo = ''
}
},
removeTodo: function(index) {
this.todos.splice(index, 1)
}
}
})
|
原理讲解
一个 Vue 实例其实正是一个 MVVM 模式中所描述的 ViewModel - 因此在文档中经常会使用 vm 这个变量名。
1
2
3
|
var vm = new Vue({
//
})
|
属性
每个 Vue 实例都会代理其 data 对象里所有的属性:
1
2
3
4
5
|
var data = { a: 1 }
var vm = new Vue({
data: data
})
vm.a === data.a // -> true
|
只有在 new Vue 中定义的属性才是被响应的,如果在实例创建之后添加新的属性到实例上,它不会触发视图更新。即双向绑定只有预先定义好的数据。
方法
除了数据属性,Vue 实例暴露了一些有用的实例属性与方法。这些属性与方法都有前缀 $,以便与代理的数据属性区分。例如:
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 (newVal, oldVal) {
// 这个回调将在 `vm.a` 改变后调用
})
|
生命周期
Vue 实例在创建时有一系列初始化步骤,并且把这些步骤的节点暴露出来。
1
2
3
4
5
6
7
8
|
var vm = new Vue({
data: {
a: 1
},
created: () {
console.log(this.a); // `this` 指向 vm 实例
}
})
|
也有一些其它的节点,在实例生命周期的不同阶段调用,如 compiled、 ready 、destroyed。
数据绑定
文本
使用 “Mustache” 语法(双大括号)
1
|
<span>Message: {{ msg }}</span>
|
你也可以只处理单次插值,今后的数据变化就不会再引起插值更新了:
1
|
<span>This will never change: {{* msg }}</span>
|
html
vue1.x中用法
双 Mustache 标签将数据解析为纯文本而不是 HTML。为了输出真的 HTML 字符串,需要用三 Mustache 标签:
1
|
<div>{{{ raw_html }}}</div>
|
vue2.x中用法
v-html
1
|
<div v-html="raw_html"></div>
|
html标签属性
vue1.x用法
1
|
<div id="item-{{ id }}"></div>
|
vue2.x用法
1
|
<div v-bind:id="id"></div>
|
js表达式
Vue.js 在数据绑定内支持全功能的 JavaScript 表达式:
1
2
3
4
5
|
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
|
这些表达式将在所属的 Vue 实例的作用域内计算。一个限制是每个绑定只能包含单个表达式,因此下面的语句是无效的:
1
2
3
4
5
|
<!-- 这是一个语句,不是一个表达式: -->
{{ var a = 1 }}
<!-- 流程控制也不可以,可改用三元表达式 -->
{{ if (ok) { return message } }}
|
过滤器 |
Vue.js 允许在表达式后添加可选的“过滤器 (Filter) ”,以“管道符”指示:
1
2
3
4
5
6
7
8
9
10
11
12
|
{{ message | capitalize }}
new Vue({
// ...
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
})
|
Vue 2.x 中,过滤器只能在 mustache 绑定中使用。为了在指令绑定中实现同样的行为,你应该使用计算属性。
并且只能通过 Vue.filter 来定义
Vue.js 需要通过自定义来使用。
1
2
3
4
5
6
7
|
// filter.vue
<script>
import Vue from 'vue'
Vue.filter('myfilter', function (value) {
return value + '-filter'
})
</script>
|
过滤器可以串联:
1
|
{{ message | filterA | filterB }}
|
过滤器也可以接受参数:
1
2
|
// message 为 filterA 的第一个参数,'arg1' 字符串为第二个参数, arg2为变量参数
{{ message | filterA 'arg1' arg2 }}
|
指令 v-
1
|
<p> v-if="greeting">Hello</p>
|
有些指令可以在其名称后面带一个“参数” ,中间放一个冒号隔开
1
2
|
<a v-bind:href="url"></a>
<a v-on:click="doSomething"></a>
|
还可添加修饰符,修饰符 (Modifiers) 是以半角句号 . 开始的特殊后缀,用于表示指令应当以特殊方式绑定。
1
2
|
//vue1.x有literal修饰符,vue2.x没有
<a v-bind:href.literal="/a/b/c"></a> // .literal 修饰符告诉指令将它的值解析为一个字面字符串而不是一个表达式
|
缩写
Vue.js 为两个最常用的指令 v-bind 和 v-on 提供特别的缩写。
1
2
3
4
5
6
7
8
9
|
<!-- 完整语法 -->
<a v-bind:href="url"></a>
<!-- 缩写 -->
<a :href="url"></a>
<!-- 完整语法 -->
<a v-on:click="doSomething"></a>
<!-- 缩写 -->
<a @click="doSomething"></a>
|
属性计算computed
Vue提供了computed中的属性,其中的属性可以和data中的属性一样正常调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<div id="example">
a={{ a }}, b={{ b }}
</div>
var vm = new Vue({
el: '#example',
data: {
a: 1
},
computed: {
// 一个计算属性的 getter
b: () {
return this.a + 1
}
}
})
|
计算属性computed默认只是 getter,不过在需要时你也可以提供一个 setter:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// ...
computed: {
fullName: {
// getter
get: () {
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.firstName 和 vm.lastName 也会有相应更新。
class绑定
注意 v-bind:class 指令可以与普通的 class 特性共存:
1
2
3
4
5
6
|
<div class="static" v-bind:class="{ 'class-a': isA, 'class-b': isB }"></div>
data: {
isA: true,
isB: false
}
|
也可以直接绑定数据里的一个对象:
1
2
3
4
5
6
7
8
|
<div v-bind:class="classObject"></div>
data: {
classObject: {
'class-a': true,
'class-b': false
}
}
|
我们可以把一个数组传给 v-bind:class,以应用一个 class 列表:
1
2
3
4
5
6
|
<div v-bind:class="[classA, classB]">
data: {
classA: 'class-a',
classB: 'class-b'
}
|
也可以组合使用:
1
|
<div v-bind:class="[classA, { classB: isB, classC: isC }]">
|
style绑定
1
2
3
4
5
6
|
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
|
直接绑定到一个样式对象通常更好,让模板更清晰:
1
2
3
4
5
6
7
8
|
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
|
也可以使用数组语法绑定style:
1
|
<div v-bind:style="[styleObjectA, styleObjectB]">
|
最后,当 v-bind:style 使用需要厂商前缀的 CSS 属性时,如 transform,Vue.js 会自动侦测并添加相应的前缀。
v-if 和 v-else
1
2
3
4
|
<h1 v-if="ok">Yes</h1>
<h1 v-if="ok">Yes</h1>
<h1 v-else>No</h1>
|
在使用v-if对多个元素进行判断时,可以使用template, 并在template上进行判断。
1
2
3
4
5
|
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
|
v-show 和 v-else
用法和v-if类似,不过v-show的元素会保持在dom中,并且v-show不支持template语法。
并且 v-show 和 v-else 不要用在组件上,可以用两个 v-show 来进行判断。
v-for循环
vue1.x 中 v-for 有一个特殊变量 $index,是当前数组元素的索引:
1
2
3
4
5
|
<ul id="example-2">
<li v-for="item in items">
{{ parentMessage }} - {{ $index }} - {{ item.message }}
</li>
</ul>
|
另外,你可以为索引指定一个别名(如果 v-for 用于一个对象,则可以为对象的键指定一个别名):
1
2
3
|
<div v-for="(item, key) in items">
{{ key }} {{ item.message }}
</div>
|
从 1.0.17 开始可以使用 of 分隔符,更接近 JavaScript 遍历器语法:
1
|
<div v-for="item of items"></div>
|
template v-for
可以将 v-for 用在 标签上,以渲染一个包含多个元素的块。
1
2
3
4
5
6
|
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider"></li>
</template>
</ul>
|
v-for对象循环
也可以使用 v-for 遍历对象。除了 $index 之外,作用域内还可以访问另外一个特殊变量 $key。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<ul id="repeat-object" class="demo">
<li v-for="value in object">
{{ $key }} : {{ value }}
</li>
</ul>
new Vue({
el: '#repeat-object',
data: {
object: {
FirstName: 'John',
LastName: 'Doe',
Age: 30
}
}
})
|
也可以给对象的键提供一个别名:
1
2
3
|
<div v-for="(key, val) in object">
{{ key }} {{ val }}
</div>
|
v-for数字循环
v-for 也可以接收一个整数,此时它将重复模板数次。
1
2
3
|
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
|
数组变动渲染
Vue.js 包装了被观察数组的变异方法,故它们能触发视图更新。被包装的方法有:
1
2
3
4
5
6
7
|
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
|
也有没有被包装的方法,如 filter(), concat() 和 slice(),不会修改原始数组而是返回一个新数组。在使用非变异方法时,可以直接用新数组替换旧数组:
1
2
3
|
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
|
可能你觉得这将导致 Vue.js 弃用已有 DOM 并重新渲染整个列表——幸运的是并非如此。 Vue.js 实现了一些启发算法,以最大化复用 DOM 元素,因而用另一个数组替换数组是一个非常高效的操作。
数组变动渲染 track-by
有时需要用全新对象(例如通过 API 调用创建的对象)替换数组,可能导致重新渲染整个列表。此时,便可以使用 track-by 特性给 Vue.js 一个提示,Vue.js 因而能尽可能地复用已有实例。
1
2
3
4
5
6
7
8
9
10
|
<div v-for="item in items" track-by="_uid">
<!-- content -->
</div>
{
items: [
{ _uid: '88f869d', ... },
{ _uid: '7496c10', ... }
]
}
|
数组变动渲染 问题点
因为 JavaScript 的限制,Vue.js 不能检测到下面数组变化:
1
2
3
|
vm.items[0] = {}; //1. 直接用索引设置元素
vm.items.length = 0; //2. 修改数据的长度
Object.freeze(); //3. 数组的元素是对象并且被冻结
|
为了解决问题 (1),Vue.js 扩展了观察数组,Vue1.x为它添加了一个 $set() 方法:
1
2
|
// 与 `example1.items[0] = ...` 相同,但是能触发视图更新
example1.items.$set(0, { childMsg: 'Changed!'})
|
Vue2.x则使用如下方法:
1
2
3
|
Vue.set(example1.items, 0, {childMsg: 'Changed!'})
或
example1.items.splice(0, 1, {childMsg: 'Changed!'})
|
至于问题 (2),只需用一个空数组替换 items。
除了 $set(), Vue.js 也为观察数组添加了 $remove() 方法,用于从目标数组中查找并删除元素,在内部它调用 splice()
1
|
this.items.$remove(item)
|
问题(3),在遍历一个数组时,如果数组元素是对象并且对象用 Object.freeze() 冻结,你需要明确指定 track-by。在这种情况下如果 Vue.js 不能自动追踪对象,将给出一条警告。
事件绑定
绑定方法
用 v-on 指令监听 DOM 事件:
1
2
3
4
5
6
7
8
9
10
|
<div id="example">
<button v-on:click="greet">Greet</button>
</div>
methods: {
greet: function (event) {
alert('Hello ' + this.name + '!') // 方法内 `this` 指向 vm
alert(event.target.tagName) // `event` 是原生 DOM 事件
}
}
|
内联语句
可以让绑定的方法带上参数:
1
2
3
4
5
6
7
8
9
10
|
<div id="example-2">
<button v-on:click="say('hi')">Say Hi</button>
<button v-on:click="say('what')">Say What</button>
</div>
methods: {
say: function (msg) {
alert(msg)
}
}
|
有时也需要在内联语句处理器中访问原生 DOM 事件。可以用特殊变量 $event 把它传入方法:
1
2
3
4
5
6
7
8
|
<button v-on:click="say('hello!', $event)">Submit</button>
methods: {
say: function (msg, event) {
// 现在我们可以访问原生事件对象
event.preventDefault()
}
}
|
事件修饰符
在事件处理器中经常需要调用 event.preventDefault() 或 event.stopPropagation(),为了解决这个问题,Vue.js 为 v-on 提供两个 事件修饰符:.prevent 与 .stop。
1
2
3
4
5
6
7
8
9
10
11
|
<!-- 阻止单击事件冒泡 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat">
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
|
1.0.16 添加了两个额外的修饰符:
1
2
3
4
5
|
<!-- 添加事件侦听器时使用 capture 模式 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当事件在该元素本身(而不是子元素)触发时触发回调 -->
<div v-on:click.self="doThat">...</div>
|
按键修饰符
在监听键盘事件时,我们经常需要检测 keyCode。Vue.js 允许为 v-on 添加按键修饰符:
1
2
|
<!-- 只有在 keyCode 是 13 时调用 vm.submit() -->
<input v-on:keyup.13="submit">
|
记住所有的 keyCode 比较困难,Vue.js 为最常用的按键提供别名:
1
2
3
4
5
|
<!-- 同上 -->
<input v-on:keyup.enter="submit">
<!-- 缩写语法 -->
<input @keyup.enter="submit">
|
全部的按键别名:
1
2
3
4
5
6
7
8
9
|
enter
tab
delete
esc
space
up
down
left
right
|
1.0.8+: 支持单字母按键别名。
1.0.17+: 可以自定义按键别名:
1
2
|
// 可以使用 @keyup.f1
Vue.directive('on').keyCodes.f1 = 112
|
表单绑定
表单绑定可以通过v-model来对 input, radio, select, checkbox, textarea来进行数据绑定。
input
1
2
3
|
<!-- input -->
<span>Message is: {{ message }}</span>
<input type="text" v-model="message" placeholder="edit me">
|
textarea
1
2
3
|
<!-- textarea -->
<p>{{ message }}</p>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
|
checkbox
1
2
3
|
<!-- checkbox -->
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
|
多个checkbox
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<!-- 多个checkbox -->
<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 | json }}</span>
new Vue({
el: '...',
data: {
checkedNames: []
}
})
|
radio
1
2
3
4
5
6
7
8
|
<!-- radio -->
<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>
|
select单选
1
2
3
4
5
6
7
|
<!-- select单选 -->
<select v-model="selected">
<option selected>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
|
select多选
1
2
3
4
5
6
7
8
|
<!-- select多选 -->
<select v-model="selected" multiple>
<option selected>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected: {{ selected | json }}</span>
|
布尔值转字符串
1
2
3
4
5
|
<!-- 布尔值设置字符串 -->
<input type="checkbox" v-model="toggle" v-bind:true-value="a" v-bind:false-value="b">
vm.toggle === vm.a // 当选中时
vm.toggle === vm.b // 当没有选中时
|
表单参数
在html上设置相应功能参数。
lazy
在默认情况下,v-model 在input 事件中同步输入框值与数据,可以添加一个特性 lazy,从而改到在 change 事件中同步:
1
2
|
<!-- 在 "change" 而不是 "input" 事件中更新 -->
<input v-model="msg" lazy>
|
number
如果想自动将用户的输入转为 Number 类型(如果原值的转换结果为 NaN 则返回原值),可以添加一个特性 number:
1
|
<input v-model="age" number>
|
trim
vue2.x中如果要自动过滤用户输入的首尾空格,可以添加 trim 修饰符到 v-model 上过滤输入:
1
|
<input v-model.trim="msg">
|
debounce
debounce 设置一个最小的延时,在每次敲击之后延时同步输入框的值与数据。如果每次更新都要进行高耗操作(例如在输入提示中 Ajax 请求),它较为有用。
1
|
<input v-model="msg" debounce="500">
|
注意 debounce 参数不会延迟 input 事件:它延迟“写入”底层数据。因此在使用 debounce 时应当用 vm.$watch() 响应数据的变化。若想延迟 DOM 事件,应当使用 debounce 过滤器。
CSS过渡
Vue.js 会在适当的时机为你触发 CSS 过渡或动画,你也可以提供相应的 JavaScript 钩子函数在过渡过程中执行自定义的 DOM 操作。
为了应用过渡效果,需要在目标元素上使用 transition 特性:
1
|
<div v-if="show" transition="my-transition"></div>
|
transition 特性可以与下面资源一起用:
1
2
3
4
5
|
v-if
v-show
v-for (只在插入和删除时触发,使用 vue-animated-list 插件)
动态组件 (介绍见组件)
在组件的根节点上,并且被 Vue 实例 DOM 方法(如 vm.$appendTo(el))触发。
|
当插入或删除带有过渡的元素时,Vue 将:
尝试以 ID “my-transition” 查找 JavaScript 过渡钩子对象——通过 Vue.transition(id, hooks) 或 transitions 选项注册。如果找到了,将在过渡的不同阶段调用相应的钩子。
自动嗅探目标元素是否有 CSS 过渡或动画,并在合适时添加/删除 CSS 类名。
如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作(插入/删除)在下一帧中立即执行。
CSS过渡示例
vuejs为css过渡默认添加了一些过渡,也可以在某些行为上自定义添加过渡。
Vue1.x:
1
|
<div v-if="show" transition="expand">hello</div>
|
Vue2.x:
1
2
3
4
5
|
<div>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
|
然后为 .expand-transition, .expand-enter 和 .expand-leave 添加 CSS 规则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/* 必需 */
.expand-transition {
transition: all .3s ease;
height: 30px;
padding: 10px;
background-color:
overflow: hidden;
}
/* .expand-enter 定义进入的开始状态 */
/* .expand-leave 定义离开的结束状态 */
.expand-enter, .expand-leave {
height: 0;
padding: 0 10px;
opacity: 0;
}
|
可以在同一元素上通过动态绑定实现不同的过渡:
1
2
3
4
5
6
7
8
9
|
<div v-if="show" :transition="transitionName">hello</div>
new Vue({
el: '...',
data: {
show: false,
transitionName: 'fade'
}
})
|
另外,可以提供 JavaScript 各个状态钩子:
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
|
Vue.transition('expand', {
beforeEnter: function (el) {
el.textContent = 'beforeEnter'
},
enter: function (el) {
el.textContent = 'enter'
},
afterEnter: function (el) {
el.textContent = 'afterEnter'
},
enterCancelled: function (el) {
// handle cancellation
},
beforeLeave: function (el) {
el.textContent = 'beforeLeave'
},
leave: function (el) {
el.textContent = 'leave'
},
afterLeave: function (el) {
el.textContent = 'afterLeave'
},
leaveCancelled: function (el) {
// handle cancellation
}
})
|
过渡的css类名
类名的添加和切换取决于 transition 特性的值。比如 transition=”fade”,会有三个 CSS 类名:
1
2
3
|
.fade-transition 始终保留在元素上。
.fade-enter 定义进入过渡的开始状态。只应用一帧然后立即删除。
.fade-leave 定义离开过渡的结束状态。在离开过渡开始时生效,在它结束后删除。
|
如果 transition 特性没有值,类名默认是 .v-transition, .v-enter 和 .v-leave。
自定义过渡类名
1.0.14 新增。
我们可以在过渡的 JavaScript 定义中声明自定义的 CSS 过渡类名。这些自定义类名会覆盖默认的类名。当需要和第三方的 CSS 动画库,比如 Animate.css 配合时会非常有用:
1
2
3
4
5
6
|
<div v-show="ok" class="animated" transition="bounce">Watch me bounce</div>
Vue.transition('bounce', {
enterClass: 'bounceInLeft',
leaveClass: 'bounceOutRight'
})
|
显式声明css过渡类型
1.0.14 新增
Vue.js 需要给过渡元素添加事件侦听器来侦听过渡何时结束。基于所使用的 CSS,该事件要么是 transitionend,要么是 animationend。如果你只使用了两者中的一种,那么 Vue.js 将能够根据生效的 CSS 规则自动推测出对应的事件类型。但是,有些情况下一个元素可能需要同时带有两种类型的动画。比如你可能希望让 Vue 来触发一个 CSS animation,同时该元素在鼠标悬浮时又有 CSS transition 效果。这样的情况下,你需要显式地声明你希望 Vue 处理的动画类型 (animation 或是 transition):
1
2
3
4
|
Vue.transition('bounce', {
// 该过渡效果将只侦听 `animationend` 事件
type: 'animation'
})
|
过渡流程详解
当 show 属性改变时,Vue.js 将相应地插入或删除
元素,按照如下规则改变过渡的 CSS 类名:
- 如果 show 变为 false,Vue.js 将:
- 调用 beforeLeave 钩子;
- 添加 v-leave 类名到元素上以触发过渡;
- 调用 leave 钩子;
- 等待过渡结束(监听 transitionend 事件);
- 从 DOM 中删除元素并删除 v-leave 类名;
- 调用 afterLeave 钩子。
- 如果 show 变为 true,Vue.js 将:
- 调用 beforeEnter 钩子;
- 添加 v-enter 类名到元素上;
- 把它插入 DOM;
- 调用 enter 钩子;
- 强制一次 CSS 布局,让 v-enter 确实生效。然后删除 v-enter 类名,以触发过渡,回到元素的原始状态;
- 等待过渡结束;
- 调用 afterEnter 钩子。
另外,如果在它的进入过渡还在进行中时删除元素,将调用 enterCancelled 钩子,以清理变动或 enter 创建的计时器。反过来对于离开过渡亦如是。
上面所有的钩子函数在调用时,它们的 this 均指向其所属的 Vue 实例。编译规则:过渡在哪个上下文中编译,它的 this 就指向哪个上下文。
最后,enter 和 leave 可以有第二个可选的回调参数,用于显式控制过渡如何结束。因此不必等待 CSS transitionend 事件, Vue.js 将等待你手工调用这个回调,以结束过渡。例如:
1
2
3
4
5
6
7
8
9
|
enter: function (el) {
// 没有第二个参数
// 由 CSS transitionend 事件决定过渡何时结束
}
enter: function (el, done) {
// 有第二个参数
// 过渡只有在调用 `done` 时结束
}
|
当多个元素一起过渡时,Vue.js 会批量处理,只强制一次布局。
CSS动画
CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除。
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
|
<span v-show="show" transition="bounce">Look at me!</span>
.bounce-transition {
display: inline-block; /* 否则 scale 动画不起作用 */
}
.bounce-enter {
animation: bounce-in .5s;
}
.bounce-leave {
animation: bounce-out .5s;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
@keyframes bounce-out {
0% {
transform: scale(1);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(0);
}
}
|
Javascript过渡
也可以只使用 JavaScript 钩子,不用定义任何 CSS 规则。当只使用 JavaScript 过渡时,enter 和 leave 钩子需要调用 done 回调,否则它们将被同步调用,过渡将立即结束。
为 JavaScript 过渡显式声明 css: false 是个好主意,Vue.js 将跳过 CSS 检测。这样也会阻止无意间让 CSS 规则干扰过渡。
在下例中我们使用 jQuery 注册一个自定义的 JavaScript 过渡:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
Vue.transition('fade', {
css: false,
enter: function (el, done) {
// 元素已被插入 DOM
// 在动画结束后调用 done
$(el)
.css('opacity', 0)
.animate({ opacity: 1 }, 1000, done)
},
enterCancelled: function (el) {
$(el).stop()
},
leave: function (el, done) {
// 与 enter 相同
$(el).animate({ opacity: 0 }, 1000, done)
},
leaveCancelled: function (el) {
$(el).stop()
}
})
|
然后用 transition 特性中:
1
|
<p transition="fade"></p>
|
渐近过渡
transition 与 v-for 一起用时可以创建渐近过渡。给过渡元素添加一个特性 stagger, enter-stagger 或 leave-stagger:
1
|
<div v-for="item in list" transition="stagger" stagger="100"></div>
|
或者,提供一个钩子 stagger, enter-stagger 或 leave-stagger,以更好的控制:
1
2
3
4
5
6
7
|
Vue.transition('stagger', {
stagger: function (index) {
// 每个过渡项目增加 50ms 延时
// 但是最大延时限制为 300ms
return Math.min(300, index * 50)
}
})
|
组件
组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。
注册
我们可以用 Vue.extend() 创建一个组件构造器:
1
2
3
|
var MyComponent = Vue.extend({
// 选项...
})
|
要把这个构造器用作组件,需要用 Vue.component(tag, constructor) 注册 :
1
2
|
// 全局注册组件,tag 为 my-component
Vue.component('my-component', MyComponent)
|
对于自定义标签名字,Vue.js 不强制要求遵循 W3C 规则(小写,并且包含一个短杠),尽管遵循这个规则比较好。
组件在注册之后,便可以在父实例的模块中以自定义元素 的形式使用。要确保在初始化根实例之前注册了组件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<div id="example">
<my-component></my-component>
</div>
// 定义
var MyComponent = Vue.extend({
template: '<div>A custom component!</div>'
})
// 注册
Vue.component('my-component', MyComponent)
// 创建根实例
new Vue({
el: '#example'
})
|
渲染为:
1
2
3
|
<div id="example">
<div>A custom component!</div>
</div>
|
局部注册
不需要全局注册每个组件。可以让组件只能用在其它组件内,用实例选项 components 注册:
1
2
3
4
5
6
7
8
9
|
var Child = Vue.extend({ /* ... */ })
var Parent = Vue.extend({
template: '...',
components: {
// <my-component> 只能用在父组件模板内
'my-component': Child
}
})
|
这种封装也适用于其它资源,如指令、过滤器和过渡。
注册语法糖
为了让事件更简单,可以直接传入选项对象而不是构造器给 Vue.component() 和 component 选项。Vue.js 在背后自动调用 Vue.extend():
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 在一个步骤中扩展与注册
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
// 局部注册也可以这么做
var Parent = Vue.extend({
components: {
'my-component': {
template: '<div>A custom component!</div>'
}
}
})
|
组件选项注意
传入 Vue 构造器的多数选项也可以用在 Vue.extend() 中,不过有两个特例: data 和 el。试想如果我们简单地把一个对象作为 data 选项传给 Vue.extend():
1
2
3
4
|
var data = { a: 1 }
var MyComponent = Vue.extend({
data: data
})
|
这么做的问题是 MyComponent 所有的实例将共享同一个 data 对象!这基本不是我们想要的,因此我们应当使用一个函数作为 data 选项,让这个函数返回一个新对象:
1
2
3
4
5
|
var MyComponent = Vue.extend({
data: function () {
return { a: 1 }
}
})
|
同理,el 选项用在 Vue.extend() 中时也须是一个函数。
模版解析
Vue 的模板是 DOM 模板,使用浏览器原生的解析器而不是自己实现一个。相比字符串模板,DOM 模板有一些好处,但是也有问题,它必须是有效的 HTML 片段。一些 HTML 元素对什么元素可以放在它里面有限制。常见的限制:
1
2
3
4
5
|
a 不能包含其它的交互元素(如按钮,链接)
ul 和 ol 只能直接包含 li
select 只能包含 option 和 optgroup
table 只能直接包含 thead, tbody, tfoot, tr, caption, col, colgroup
tr 只能直接包含 th 和 td
|
在实际中,这些限制会导致意外的结果。尽管在简单的情况下它可能可以工作,但是你不能依赖自定义组件在浏览器验证之前的展开结果。例如
1
2
3
|
<my-select><option>...</option></my-select> 不是有效的模板,即使 my-select 组件最终展开为 <select>...</select>。
另一个结果是,自定义标签(包括自定义元素和特殊标签,如
<component>、<template>、 <partial> )不能用在 ul, select, table 等对内部元素有限制的标签内。放在这些元素内部的自定义标签将被提到元素的外面,因而渲染不正确。
|
对于自定义元素,应当使用 is 特性:
1
2
3
|
<table>
<tr is="my-component"></tr>
</table>
|
template 不能用在 table 内,这时应使用 tbody,table 可以有多个tbody:
1
2
3
4
5
6
|
<table>
<tbody v-for="item in items">
<tr>Even row</tr>
<tr>Odd row</tr>
</tbody>
</table>
|
Props
组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。可以使用 props 把数据传给子组件。
“prop” 是组件数据的一个字段,期望从父组件传下来。子组件需要显式地用 props 选项 声明 props:
1
2
3
4
5
6
7
|
Vue.component('child', {
// 声明 props
props: ['msg'],
// prop 可以用在模板内
// 可以用 `this.msg` 设置
template: '<span>{{ msg }}</span>'
})
|
然后向它传入一个普通字符串:
1
|
<child msg="hello!"></child>
|
动态Props
类似于用 v-bind 绑定 HTML 特性到一个表达式,也可以用 v-bind 绑定动态 Props 到父组件的数据。每当父组件的数据变化时,也会传导给子组件:
1
2
3
4
5
|
<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
|
使用 v-bind 的缩写语法通常更简单:
1
|
<child :my-message="parentMsg"></child>
|
初学者常犯的一个错误是使用字面量语法传递数值:
1
2
|
<!-- 传递了一个字符串 "1" -->
<comp some-prop="1"></comp>
|
因为它是一个字面 prop,它的值以字符串 “1” 而不是以实际的数字传下去。如果想传递一个实际的 JavaScript 数字,需要使用动态语法,从而让它的值被当作 JavaScript 表达式计算:
1
2
|
<!-- 传递实际的数字 -->
<comp :some-prop="1"></comp>
|
Prop双向绑定
prop 默认是单向绑定:当父组件的属性变化时,将传导给子组件,但是反过来不会。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。不过,也可以使用 .sync 或 .once 绑定修饰符显式地强制双向或单次绑定:
比较语法:
1
2
3
4
5
6
7
8
|
<!-- 默认为单向绑定 -->
<child :msg="parentMsg"></child>
<!-- 双向绑定 Vue1.x Vue2.x无此功能, 使用自定义事件传递数据回父组件 -->
<child :msg.sync="parentMsg"></child>
<!-- 单次绑定 Vue1.x Vue2.x无此功能, 使用自定义事件传递数据回父组件 -->
<child :msg.once="parentMsg"></child>
|
双向绑定会把子组件的 msg 属性同步回父组件的 parentMsg 属性。单次绑定在建立之后不会同步之后的变化。
注意如果 prop 是一个对象或数组,是按引用传递。在子组件内修改它会影响父组件的状态,不管是使用哪种绑定类型。
Prop验证
组件可以为 props 指定验证要求。当组件给其他人使用时这很有用,因为这些验证要求构成了组件的 API,确保其他人正确地使用组件。此时 props 的值是一个对象,包含验证要求:
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
46
47
48
|
Vue.component('example', {
props: {
// 基础类型检测 (`null` 意思是任何类型都可以)
propA: Number,
// 多种类型 (1.0.21+)
propM: [String, Number],
// 必需且是字符串
propB: {
type: String,
required: true
},
// 数字,有默认值
propC: {
type: Number,
default: 100
},
// 对象/数组的默认值应当由一个函数返回
propD: {
type: Object,
default: function () {
return { msg: 'hello' }
}
},
// 指定这个 prop 为双向绑定
// 如果绑定类型不对将抛出一条警告
propE: {
twoWay: true
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10
}
},
// 转换函数(1.0.12 新增)
// 在设置值之前转换值
propG: {
coerce: function (val) {
return val + '' // 将值转换为字符串
}
},
propH: {
coerce: function (val) {
return JSON.parse(val) // 将 JSON 字符串转换为对象
}
}
}
})
|
type 可以是下面原生构造器:
1
2
3
4
5
6
|
String
Number
Boolean
Function
Object
Array
|
type 也可以是一个自定义构造器,使用 instanceof 检测。
camelCase 和 kebab-case
HTML 特性不区分大小写。名字形式为 camelCase 的 prop 用作特性时,需要转为 kebab-case(短横线隔开):
1
2
3
4
5
6
7
8
|
Vue.component('child', {
// camelCase in JavaScript
props: ['myMessage'],
template: '<span>{{ myMessage }}</span>'
})
<!-- kebab-case in HTML -->
<child my-message="hello!"></child>
|
父子组件通信-父链
子组件可以用 this.$parent 访问它的父组件。根实例的后代可以用 this.$root 访问它。父组件有一个数组 this.$children,包含它所有的子元素。
尽管可以访问父链上任意的实例,不过子组件应当避免直接依赖父组件的数据,尽量显式地使用 props 传递数据。另外,在子组件中修改父组件的状态是非常糟糕的做法,因为:
- 这让父组件与子组件紧密地耦合;
- 只看父组件,很难理解父组件的状态。因为它可能被任意子组件修改!理想情况下,只有组件自己能修改它的状态。
父子组件通信-自定义事件
Vue 实例实现了一个自定义事件接口,用于在组件树中通信。这个事件系统独立于原生 DOM 事件,用法也不同。
每个 Vue 实例都是一个事件触发器:
1
2
3
4
|
使用 $on() 监听事件;
使用 $emit() 在它上面触发事件;
使用 $dispatch() 派发事件,事件沿着父链冒泡; // Vue1.x适用,Vue2.x中使用vue.x
使用 $broadcast() 广播事件,事件向下传导给所有的后代。 // Vue1.x适用,Vue2.x中使用vue.x
|
不同于 DOM 事件,Vue 事件在冒泡过程中第一次触发回调之后自动停止冒泡,除非回调明确返回 true。
简单例子:
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
|
<!-- 子组件模板 -->
<template id="child-template">
<input v-model="msg">
<button v-on:click="notify">Dispatch Event</button>
</template>
<!-- 父组件模板 -->
<div id="events-example">
<p>Messages: {{ messages | json }}</p>
<child></child>
</div>
// 注册子组件
// 将当前消息派发出去
Vue.component('child', {
template: '#child-template',
data: function () {
return { msg: 'hello' }
},
methods: {
notify: function () {
if (this.msg.trim()) {
this.$dispatch('child-msg', this.msg)
this.msg = ''
}
}
}
})
// 初始化父组件
// 将收到消息时将事件推入一个数组
var parent = new Vue({
el: '#events-example',
data: {
messages: []
},
// 在创建实例时 `events` 选项简单地调用 `$on`
events: {
'child-msg': function (msg) {
// 事件回调内的 `this` 自动绑定到注册它的实例上
this.messages.push(msg)
}
}
})
|
非父子组件通信
Vue2.x
使用v-on绑定自定义事件
Vue1.x
上例非常好,不过从父组件的代码中不能直观的看到 “child-msg” 事件来自哪里。如果我们在模板中子组件用到的地方声明事件处理器会更好。为此子组件可以用 v-on 监听自定义事件:
1
|
<child v-on:child-msg="handleIt"></child>
|
这样就很清楚了:当子组件触发了 “child-msg” 事件,父组件的 handleIt 方法将被调用。所有影响父组件状态的代码放到父组件的 handleIt 方法中;子组件只关注触发事件。
Vue2.x
父组件是使用 props 传递数据给子组件,但如果子组件要把数据传递回去,应该怎样做?那就是自定义事件!
每个 Vue 实例都实现了事件接口(Events interface),即:
使用 $on(eventName) 监听事件
使用 $emit(eventName) 触发事件
另外,父组件可以在使用子组件的地方直接用 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
25
26
27
28
29
30
31
32
|
// 父组件
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
// 子组件
Vue.component('button-counter', {
template: '<button v-on:click="increment">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
increment: function () {
this.counter += 1
this.$emit('increment')
}
},
})
|
若只想监听原生事件,则用 .native 修饰 v-on 。
1
|
<my-component v-on:click.native="doTheThing"></my-component>
|
子组件索引
尽管有 props 和 events,但是有时仍然需要在 JavaScript 中直接访问子组件。为此可以使用 v-ref 为子组件指定一个索引 ID。例如:
1
2
3
4
5
6
7
|
<div id="parent">
<user-profile v-ref:profile></user-profile>
</div>
var parent = new Vue({ el: '#parent' })
// 访问子组件
var child = parent.$refs.profile
|
v-ref 和 v-for 一起用时,ref 是一个数组或对象,包含相应的子组件。
使用Slot分发内容
在使用组件时,常常要像这样组合它们:
1
2
3
4
|
<app>
<app-header></app-header>
<app-footer></app-footer>
</app>
|
注意两点:
- 组件不知道它的挂载点会有什么内容,挂载点的内容是由 的父组件决定的。
- 组件很可能有它自己的模板。
为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个处理称为内容分发(或 “transclusion”,如果你熟悉 Angular)。Vue.js 实现了一个内容分发 API,参照了当前 Web 组件规范草稿,使用特殊的 元素作为原始内容的插槽。
编译作用域
在深入内容分发 API 之前,我们先明确内容的编译作用域。假定模板为:
1
2
3
|
<child-component>
{{ msg }}
</child-component>
|
msg 应该绑定到父组件的数据,还是绑定到子组件的数据?答案是父组件。组件作用域简单地说是:
父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译
一个常见错误是试图在父组件模板内将一个指令绑定到子组件的属性/方法:
1
2
|
<!-- 无效 -->
<child-component v-show="someChildProperty"></child-component>
|
假定 someChildProperty 是子组件的属性,上例不会如预期那样工作。父组件模板不应该知道子组件的状态。
如果要绑定子组件内的指令到一个组件的根节点,应当在它的模板内这么做:
1
2
3
4
5
6
7
8
9
|
Vue.component('child-component', {
// 有效,因为是在正确的作用域内
template: '<div v-show="someChildProperty">Child</div>',
data: function () {
return {
someChildProperty: true
}
}
})
|
类似地,分发内容是在父组件作用域内编译。
单个Slot
父组件的内容将被抛弃,除非子组件模板包含 。如果子组件模板只有一个没有特性的 slot,父组件的整个内容将插到 slot 所在的地方并替换它。
标签的内容视为回退内容。回退内容在子组件的作用域内编译,当宿主元素为空并且没有内容供插入时显示这个回退内容。
假定 my-component 组件有下面模板:
1
2
3
4
5
6
|
<div>
<h1>This is my component!</h1>
<slot>
如果没有分发内容则显示我。
</slot>
</div>
|
父组件模板:
1
2
3
4
|
<my-component>
<p>This is some original content</p>
<p>This is some more original content</p>
</my-component>
|
渲染结果:
1
2
3
4
5
|
<div>
<h1>This is my component!</h1>
<p>This is some original content</p>
<p>This is some more original content</p>
</div>
|
具名Slot
元素可以用一个特殊特性 name 配置如何分发内容。多个 slot 可以有不同的名字。具名 slot 将匹配内容片段中有对应 slot 特性的元素。
仍然可以有一个匿名 slot,它是默认 slot,作为找不到匹配的内容片段的回退插槽。如果没有默认的 slot,这些找不到匹配的内容片段将被抛弃。
例如,假定我们有一个 multi-insertion 组件,它的模板为:
1
2
3
4
5
|
<div>
<slot name="one"></slot>
<slot></slot>
<slot name="two"></slot>
</div>
|
父组件模板:
1
2
3
4
5
|
<multi-insertion>
<p slot="one">One</p>
<p slot="two">Two</p>
<p>Default A</p>
</multi-insertion>
|
渲染结果为:
1
2
3
4
5
|
<div>
<p slot="one">One</p>
<p>Default A</p>
<p slot="two">Two</p>
</div>
|
在组合组件时,内容分发 API 是非常有用的机制。
动态组件
多个组件可以使用同一个挂载点,然后动态地在它们之间切换。使用保留的 元素,动态地绑定到它的 is 特性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
new Vue({
el: 'body',
data: {
currentView: 'home'
},
components: {
home: { /* ... */ },
posts: { /* ... */ },
archive: { /* ... */ }
}
})
<component :is="currentView">
<!-- 组件在 vm.currentview 变化时改变 -->
</component>
|
keep-alive
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数:
1
2
3
|
<component :is="currentView" keep-alive>
<!-- 非活动组件将被缓存 -->
</component>
|
activate钩子
在切换组件时,切入组件在切入前可能需要进行一些异步操作。为了控制组件切换时长,给切入组件添加 activate 钩子:
1
2
3
4
5
6
7
8
9
|
Vue.component('activate-example', {
activate: function (done) {
var self = this
loadDataAsync(function (data) {
self.someData = data
done()
})
}
})
|
注意 activate 钩子只作用于动态组件切换或静态组件初始化渲染的过程中,不作用于使用实例方法手工插入的过程中。
transition-mode
transition-mode 特性用于指定两个动态组件之间如何过渡。
在默认情况下,进入与离开平滑地过渡。这个特性可以指定另外两种模式:
- in-out:新组件先过渡进入,等它的过渡完成之后当前组件过渡出去。
- out-in:当前组件先过渡出去,等它的过渡完成之后新组件过渡进入。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!-- 先淡出再淡入 -->
<component
:is="view"
transition="fade"
transition-mode="out-in">
</component>
.fade-transition {
transition: opacity .3s ease;
}
.fade-enter, .fade-leave {
opacity: 0;
}
|
杂项
- 组件和v-for
自定义组件可以像普通元素一样直接使用 v-for:
1
|
<my-component v-for="item in items"></my-component>
|
但是,不能传递数据给组件,因为组件的作用域是孤立的。为了传递数据给组件,应当使用 props:
1
2
3
4
5
|
<my-component
v-for="item in items"
:item="item"
:index="$index">
</my-component>
|
不自动把 item 注入组件的原因是这会导致组件跟当前 v-for 紧密耦合。显式声明数据来自哪里可以让组件复用在其它地方。
-
编写可复用组件
在编写组件时,记住是否要复用组件有好处。一次性组件跟其它组件紧密耦合没关系,但是可复用组件应当定义一个清晰的公开接口。
Vue.js 组件 API 来自三部分——prop,事件和 slot:
prop 允许外部环境传递数据给组件;
事件 允许组件触发外部环境的 action;
slot 允许外部环境插入内容到组件的视图结构内。
使用 v-bind 和 v-on 的简写语法,模板的缩进清楚且简洁:
1
2
3
4
5
6
7
8
9
|
<my-component
:foo="baz"
:bar="qux"
@event-a="doThis"
@event-b="doThat">
<!-- content -->
<img slot="icon" src="...">
<p slot="main-text">Hello!</p>
</my-component>
|
-
异步组件
在大型应用中,我们可能需要将应用拆分为小块,只在需要时才从服务器下载。为了让事情更简单,Vue.js 允许将组件定义为一个工厂函数,动态地解析组件的定义。Vue.js 只在组件需要渲染时触发工厂函数,并且把结果缓存起来,用于后面的再次渲染。例如:
1
2
3
4
5
6
7
|
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
|
工厂函数接收一个 resolve 回调,在收到从服务器下载的组件定义时调用。也可以调用 reject(reason) 指示加载失败。这里 setTimeout 只是为了演示。怎么获取组件完全由你决定。推荐配合使用 Webpack 的代码分割功能:
1
2
3
4
5
6
|
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 require 语法告诉 webpack
// 自动将编译后的代码分割成不同的块,
// 这些块将通过 ajax 请求自动下载。
require(['./my-async-component'], resolve)
})
|
- 资源命名约定
一些资源,如组件和指令,是以 HTML 特性或 HTML 自定义元素的形式出现在模板中。因为 HTML 特性的名字和标签的名字不区分大小写,所以资源的名字通常需使用 kebab-case 而不是 camelCase 的形式,这不大方便。
Vue.js 支持资源的名字使用 camelCase 或 PascalCase 的形式,并且在模板中自动将它们转为 kebab-case(类似于 prop 的命名约定):
1
2
3
4
5
6
7
8
|
// 在组件定义中
components: {
// 使用 camelCase 形式注册
myComponent: { /*... */ }
}
<!-- 在模板中使用 kebab-case 形式 -->
<my-component></my-component>
|
ES6 对象字面量缩写 也没问题:
1
2
3
4
5
6
7
8
9
10
11
|
// PascalCase
import TextBox from './components/text-box';
import DropdownMenu from './components/dropdown-menu';
export default {
components: {
// 在模板中写作 <text-box> 和 <dropdown-menu>
TextBox,
DropdownMenu
}
}
|
- 递归组件
组件在它的模板内可以递归地调用自己,不过,只有当它有 name 选项时才可以:
1
2
3
4
5
6
7
8
|
var * = Vue.extend({
name: 'stack-overflow',
template:
'<div>' +
// 递归地调用它自己
'<stack-overflow></stack-overflow>' +
'</div>'
})
|
上面组件会导致一个错误 “max stack size exceeded”,所以要确保递归调用有终止条件。当使用 Vue.component() 全局注册一个组件时,组件 ID 自动设置为组件的 name 选项。
- 片段实例
在使用 template 选项时,模板的内容将替换实例的挂载元素。因而推荐模板的*元素始终是单个元素。
不这么写模板:
1
2
|
<div>root node 1</div>
<div>root node 2</div>
|
推荐这么写:
1
2
3
4
5
|
<div>
I have a single root node!
<div>node 1</div>
<div>node 2</div>
</div>
|
下面几种情况会让实例变成一个片断实例:
- 模板包含多个*元素。
- 模板只包含普通文本。
- 模板只包含其它组件(其它组件可能是一个片段实例)。
- 模板只包含一个元素指令,如 或 vue-router 的 。
- 模板根节点有一个流程控制指令,如 v-if 或 v-for。
这些情况让实例有未知数量的*元素,它将把它的 DOM 内容当作片断。片断实例仍然会正确地渲染内容。不过,它没有一个根节点,它的 $el 指向一个锚节点,即一个空的文本节点(在开发模式下是一个注释节点)。
但是更重要的是,组件元素上的非流程控制指令,非 prop 特性和过渡将被忽略,因为没有根元素供绑定:
1
2
3
4
5
6
7
8
|
<!-- 不可以,因为没有根元素 -->
<example v-show="ok" transition="fade"></example>
<!-- props 可以 -->
<example :prop="someData"></example>
<!-- 流程控制可以,但是不能有过渡 -->
<example v-if="ok"></example>
|
当然片断实例有它的用处,不过通常给组件一个根节点比较好。它会保证组件元素上的指令和特性能正确地转换,同时性能也稍微好些。
内联模板
如果子组件有 inline-template 特性,组件将把它的内容当作它的模板,而不是把它当作分发内容。这让模板更灵活。
1
2
3
4
|
<my-component inline-template>
<p>These are compiled as the component's own template</p>
<p>Not parent's transclusion content.</p>
</my-component>
|
但是 inline-template 让模板的作用域难以理解,并且不能缓存模板编译结果。最佳实践是使用 template 选项在组件内定义模板。
深入响应式原理
双向绑定原理-追踪变化
把一个普通对象传给 Vue 实例作为它的 data 选项,Vue.js 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。这是 ES5 特性,不能打补丁实现,这便是为什么 Vue.js 不支持 IE8 及更低版本。
用户看不到 getter/setters,但是在内部它们让 Vue.js 追踪依赖,在属性被访问和修改时通知变化。一个问题是在浏览器控制台打印数据对象时 getter/setter 的格式化不同,使用 vm.$log() 实例方法可以得到更友好的输出。
模板中每个指令/数据绑定都有一个对应的 watcher 对象,在计算过程中它把属性记录为依赖。之后当依赖的 setter 被调用时,会触发 watcher 重新计算 ,也就会导致它的关联指令更新 DOM。
模板中每个指令/数据绑定都有一个对应的 watcher 对象,在计算过程中它把属性记录为依赖。之后当依赖的 setter 被调用时,会触发 watcher 重新计算 ,也就会导致它的关联指令更新 DOM。
变化检测问题
受 ES5 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。例如:
1
2
3
4
5
6
7
8
9
10
11
|
ar data = { a: 1 }
var vm = new Vue({
data: data
})
// `vm.a` 和 `data.a` 现在是响应的
vm.b = 2
// `vm.b` 不是响应的
data.b = 2
// `data.b` 不是响应的
|
不过,有办法在实例创建之后添加属性并且让它是响应的。
对于 Vue 实例,可以使用 $set(key, value) 实例方法:
1
2
|
vm.$set('b', 2)
// `vm.b` 和 `data.b` 现在是响应的
|
对于普通数据对象,可以使用全局方法 Vue.set(object, key, value):
1
2
|
Vue.set(data, 'c', 3)
// `vm.c` 和 `data.c` 现在是响应的
|
有时你想向已有对象上添加一些属性,例如使用 Object.assign() 或 _.extend() 添加属性。但是,添加到对象上的新属性不会触发更新。这时可以创建一个新的对象,包含原对象的属性和新的属性:
1
2
|
// 不使用 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
|
也有一些数组相关的问题,之前已经在列表渲染中讲过。
初始化数据
尽管 Vue.js 提供了 API 动态地添加响应属性,还是推荐在 data 对象上声明所有的响应属性。
不这么做:
1
2
3
4
5
|
var vm = new Vue({
template: '<div>{{msg}}</div>'
})
// 然后添加 `msg`
vm.$set('msg', 'Hello!')
|
这么做:
1
2
3
4
5
6
7
8
9
|
var vm = new Vue({
data: {
// 以一个空值声明 `msg`
msg: ''
},
template: '<div>{{msg}}</div>'
})
// 然后设置 `msg`
vm.msg = 'Hello!'
|
这么做有两个原因:
- data 对象就像组件状态的模式(schema)。在它上面声明所有的属性让组件代码更易于理解。
- 添加一个*响应属性会强制所有的 watcher 重新计算,因为它之前不存在,没有 watcher 追踪它。这么做性能通常是可以接受的(特别是对比 Angular 的脏检查),但是可以在初始化时避免。
异步更新队列
Vue.js 默认异步更新 DOM。每当观察到数据变化时,Vue 就开始一个队列,将同一事件循环内所有的数据变化缓存起来。如果一个 watcher 被多次触发,只会推入一次到队列中。等到下一次事件循环,Vue 将清空队列,只进行必要的 DOM 更新。在内部异步队列优先使用 MutationObserver,如果不支持则使用 setTimeout(fn, 0)。
例如,设置了 vm.someData = ‘new value’,DOM 不会立即更新,而是在下一次事件循环清空队列时更新。我们基本不用关心这个过程,但是如果想在 DOM 状态更新后做点什么,这会有帮助。尽管 Vue.js 鼓励开发者沿着数据驱动的思路,避免直接修改 DOM,但是有时确实要这么做。为了在数据变化之后等待 Vue.js 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback) 。回调在 DOM 更新完成后调用。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<div id="example">{{msg}}</div>
var vm = new Vue({
el: '#example',
data: {
msg: '123'
}
})
vm.msg = 'new message' // 修改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})
|
vm.$nextTick() 这个实例方法比较方便,因为它不需要全局 Vue,它的回调的 this 自动绑定到当前 Vue 实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
Vue.component('example', {
template: '<span>{{msg}}</span>',
data: function () {
return {
msg: 'not updated'
}
},
methods: {
updateMessage: function () {
this.msg = 'updated'
console.log(this.$el.textContent) // => 'not updated'
this.$nextTick(function () {
console.log(this.$el.textContent) // => 'updated'
})
}
}
})
|
计算属性的奥秘
你应该注意到 Vue.js 的计算属性不是简单的 getter。计算属性持续追踪它的响应依赖。在计算一个计算属性时,Vue.js 更新它的依赖列表并缓存结果,只有当其中一个依赖发生了变化,缓存的结果才无效。因此,只要依赖不发生变化,访问计算属性会直接返回缓存的结果,而不是调用 getter。
为什么要缓存呢?假设我们有一个高耗计算属性 A,它要遍历一个巨型数组并做大量的计算。然后,可能有其它的计算属性依赖 A。如果没有缓存,我们将调用 A 的 getter 许多次,超过必要次数。
由于计算属性被缓存了,在访问它时 getter 不总是被调用。考虑下例:
1
2
3
4
5
6
7
8
9
10
|
var vm = new Vue({
data: {
msg: 'hi'
},
computed: {
example: function () {
return Date.now() + this.msg
}
}
})
|
计算属性 example 只有一个依赖:vm.msg。Date.now() 不是 响应依赖,因为它跟 Vue 的数据观察系统无关。因而,在访问 vm.example 时将发现时间戳不变,除非 vm.msg 变了。
有时希望 getter 不改变原有的行为,每次访问 vm.example 时都调用 getter。这时可以为指定的计算属性关闭缓存:
1
2
3
4
5
6
7
8
|
computed: {
example: {
cache: false,
get: function () {
return Date.now() + this.msg
}
}
}
|
现在每次访问 vm.example 时,时间戳都是新的。但是,只是在 JavaScript 中访问是这样的;数据绑定仍是依赖驱动的。如果在模块中这样绑定计算属性 ,只有响应依赖发生变化时才更新 DOM。
自定义指令
除了内置指令,Vue.js 也允许注册自定义指令。自定义指令提供一种机制将数据的变化映射为 DOM 行为。
可以用 Vue.directive(id, definition) 方法注册一个全局自定义指令,它接收两个参数指令 ID 与定义对象。也可以用组件的 directives 选项注册一个局部自定义指令。
钩子函数
定义对象可以提供几个钩子函数(都是可选的):
bind:只调用一次,在指令第一次绑定到元素上时调用。
update: 在 bind 之后立即以初始值为参数第一次调用,之后每当绑定值变化时调用,参数为新值与旧值。
unbind:只调用一次,在指令从元素上解绑时调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Vue.directive('my-directive', {
bind: function () {
// 准备工作
// 例如,添加事件处理器或只需要运行一次的高耗任务
},
update: function (newValue, oldValue) {
// 值更新时的工作
// 也会以初始值为参数调用一次
},
unbind: function () {
// 清理工作
// 例如,删除 bind() 添加的事件监听器
}
})
|
在注册之后,便可以在 Vue.js 模板中这样用(记着添加前缀 v-):
1
|
<div v-my-directive="someValue"></div>
|
当只需要 update 函数时,可以传入一个函数替代定义对象:
1
2
3
|
Vue.directive('my-directive', function (value) {
// 这个函数用作 update()
})
|
实例的指令属性
所有的钩子函数将被复制到实际的指令对象中,钩子内 this 指向这个指令对象。这个对象暴露了一些有用的属性:
- el: 指令绑定的元素。
- vm: 拥有该指令的上下文 ViewModel。
- expression: 指令的表达式,不包括参数和过滤器。
- arg: 指令的参数。
- name: 指令的名字,不包含前缀。
- modifiers: 一个对象,包含指令的修饰符。
- descriptor: 一个对象,包含指令的解析结果。
你应当将这些属性视为只读的,不要修改它们。你也可以给指令对象添加自定义属性,但是注意不要覆盖已有的内部属性。
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
|
<div id="demo" v-demo:hello.a.b="msg"></div>
Vue.directive('demo', {
bind: function () {
console.log('demo bound!')
},
update: function (value) {
this.el.innerHTML =
'name - ' + this.name + '<br>' +
'expression - ' + this.expression + '<br>' +
'argument - ' + this.arg + '<br>' +
'modifiers - ' + JSON.stringify(this.modifiers) + '<br>' +
'value - ' + value
}
})
var demo = new Vue({
el: '#demo',
data: {
msg: 'hello!'
}
})
//结果
name-demo
expression-msg
argument-hello
modifiers-{"b":true,"a":true}
value-hello
|
指令对象参数传值
如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令可以使用任意合法的 JavaScript 表达式:
1
2
3
4
5
6
|
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive('demo', function (value) {
console.log(value.color) // "white"
console.log(value.text) // "hello!"
})
|
字面修饰符
当指令使用了字面修饰符,它的值将按普通字符串处理并传递给 update 方法。update 方法将只调用一次,因为普通字符串不能响应数据变化。
1
2
3
4
5
|
<div v-demo.literal="foo bar baz">
Vue.directive('demo', function (value) {
console.log(value) // "foo bar baz"
})
|
元素指令
有时我们想以自定义元素的形式使用指令,而不是以特性的形式。这与 Angular 的 “E” 指令非常相似。元素指令可以看做是一个轻量组件。可以像下面这样注册一个自定义元素指令:
1
2
3
4
5
6
|
Vue.elementDirective('my-directive', {
// API 同普通指令
bind: function () {
// 操作 this.el...
}
})
|
不这样写:
1
|
<div v-my-directive></div>
|
这样写:
1
|
<my-directive></my-directive>
|
元素指令不能接受参数或表达式,但是它可以读取元素的特性从而决定它的行为。
迥异于普通指令,元素指令是终结性的,这意味着,一旦 Vue 遇到一个元素指令,它将跳过该元素及其子元素——只有该元素指令本身可以操作该元素及其子元素。
高级选项
params
自定义指令可以接收一个 params 数组,指定一个特性列表,Vue 编译器将自动提取绑定元素的这些特性。例如:
1
2
3
4
5
6
7
8
|
<div v-example a="hi"></div>
Vue.directive('example', {
params: ['a'],
bind: function () {
console.log(this.params.a) // -> "hi"
}
})
|
此 API 也支持动态属性。this.params[key] 会自动保持更新。另外,可以指定一个回调,在值变化时调用:
1
2
3
4
5
6
7
8
9
10
|
<div v-example v-bind:a="someValue"></div>
Vue.directive('example', {
params: ['a'],
paramWatchers: {
a: function (val, oldVal) {
console.log('a changed!')
}
}
})
|
类似于 props,指令参数的名字在 JavaScript 中使用 camelCase 风格,在 HTML 中对应使用 kebab-case 风格。例如,假设在模板里有一个参数 disable-effect,在 JavaScript 里以 disableEffect 访问它。
deep
如果自定义指令用在一个对象上,当对象内部属性变化时要触发 update,则在指令定义对象中指定 deep: true。
1
2
3
4
5
6
7
8
|
<div v-my-directive="obj"></div>
Vue.directive('my-directive', {
deep: true,
update: function (obj) {
// 在 `obj` 的嵌套属性变化时调用
}
})
|
twoWay
如果指令想向 Vue 实例写回数据,则在指令定义对象中指定 twoWay: true 。该选项允许在指令中使用 this.set(value):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Vue.directive('example', {
twoWay: true,
bind: function () {
this.handler = function () {
// 将数据写回 vm
// 如果指令这样绑定 v-example="a.b.c"
// 它将用给定值设置 `vm.a.b.c`
this.set(this.el.value)
}.bind(this)
this.el.addEventListener('input', this.handler)
},
unbind: function () {
this.el.removeEventListener('input', this.handler)
}
})
|
acceptStatement
传入 acceptStatement:true 可以让自定义指令接受内联语句,就像 v-on 那样:
1
2
3
4
5
6
7
8
9
|
<div v-my-directive="a++"></div>
Vue.directive('my-directive', {
acceptStatement: true,
update: function (fn) {
// 传入值是一个函数
// 在调用它时将在所属实例作用域内计算 "a++" 语句
}
})
|
明智地使用,因为通常你要在模板中避免副效应。
terminal
1.0.19+
Vue 通过递归遍历 DOM 树来编译模块。但是当它遇到 terminal 指令时会停止遍历这个元素的后代元素。这个指令将接管编译这个元素及其后代元素的任务。v-if 和 v-for 都是 terminal 指令。
编写自定义 terminal 指令是一个高级话题,需要较好的理解 Vue 的编译流程,但这不是说不可能编写自定义 terminal 指令。用 terminal: true 指定自定义 terminal 指令,可能还需要用 Vue.FragmentFactory 来编译 partial。下面是一个自定义 terminal 指令,它编译它的内容模板并将结果注入到页面的另一个地方:
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
|
var FragmentFactory = Vue.FragmentFactory
var remove = Vue.util.remove
var createAnchor = Vue.util.createAnchor
Vue.directive('inject', {
terminal: true,
bind: function () {
var container = document.getElementById(this.arg)
this.anchor = createAnchor('v-inject')
container.appendChild(this.anchor)
remove(this.el)
var factory = new FragmentFactory(this.vm, this.el)
this.frag = factory.create(this._host, this._scope, this._frag)
this.frag.before(this.anchor)
},
unbind: function () {
this.frag.remove()
remove(this.anchor)
}
})
<div id="modal"></div>
...
<div v-inject:modal>
<h1>header</h1>
<p>body</p>
<p>footer</p>
</div>
|
如果你想编写自定义 terminal 指令,建议你通读内置 terminal 指令的源码,如 v-if 和 v-for,以便更好地了解 Vue 的内部机制。
priority
可以给指令指定一个优先级。如果没有指定,普通指令默认是 1000, terminal 指令默认是 2000。同一个元素上优先级高的指令会比其它指令处理得早一些。优先级一样的指令按照它在元素特性列表中出现的顺序依次处理,但是不能保证这个顺序在不同的浏览器中是一致的。
可以在 API 中查看内置指令的优先级。另外,流程控制指令 v-if 和 v-for 在编译过程中始终拥有最高的优先级。
自定义过滤器
类似于自定义指令,可以用全局方法 Vue.filter() 注册一个自定义过滤器,它接收两个参数:过滤器 ID 和过滤器函数。过滤器函数以值为参数,返回转换后的值:
1
2
3
4
5
6
|
Vue.filter('reverse', function (value) {
return value.split('').reverse().join('')
})
<!-- 'abc' => 'cba' -->
<span v-text="message | reverse"></span>
|
过滤器函数可以接收任意数量的参数:
1
2
3
4
5
6
|
Vue.filter('wrap', function (value, begin, end) {
return begin + value + end
})
<!-- 'hello' => 'before hello after' -->
<span v-text="message | wrap 'before' 'after'"></span>
|
双向过滤器
目前我们使用过滤器都是在把来自模型的值显示在视图之前转换它。不过也可以定义一个过滤器,在把来自视图( 元素)的值写回模型之前转化它:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Vue.filter('currencyDisplay', {
// model -> view
// 在更新 `<input>` 元素之前格式化值
read: function(val) {
return '$'+val.toFixed(2)
},
// view -> model
// 在写回数据之前格式化值
write: function(val, oldVal) {
var number = +val.replace(/[^d.]/g, '')
return isNaN(number) ? 0 : parseFloat(number.toFixed(2))
}
})
|
动态参数
如果过滤器参数没有用引号包起来,则它会在当前 vm 作用域内动态计算。另外,过滤器函数的 this 始终指向调用它的 vm。例如:
1
2
3
4
5
6
7
|
<input v-model="userInput">
<span>{{msg | concat userInput}}</span>
Vue.filter('concat', function (value, input) {
// `input` === `this.userInput`
return value + input
})
|
上例比较简单,也可以用表达式达到相同的结果,但是对于更复杂的逻辑——需要多于一个语句,这时需要将它放到计算属性或自定义过滤器中。
内置过滤器 filterBy 和 orderBy,根据所属 Vue 实例的当前状态,过滤/排序传入的数组。
混合对象
混合以一种灵活的方式为组件提供分布复用功能。混合对象可以包含任意的组件选项。当组件使用了混合对象时,混合对象的所有选项将被“混入”组件自己的选项中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// 定义一个混合对象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定义一个组件,使用这个混合对象
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // -> "hello from mixin!"
|
选项合并
当混合对象与组件包含同名选项时,这些选项将以适当的策略合并。例如,同名钩子函数被并入一个数组,因而都会被调用。另外,混合的钩子将在组件自己的钩子之前调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var mixin = {
created: function () {
console.log('mixin hook called')
}
}
new Vue({
mixins: [mixin],
created: function () {
console.log('component hook called')
}
})
// -> "mixin hook called"
// -> "component hook called"
|
值为对象的选项,如 methods, components 和 directives 将合并到同一个对象内。如果键冲突则组件的选项优先。
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
|
var mixin = {
methods: {
foo: function () {
console.log('foo')
},
conflicting: function () {
console.log('from mixin')
}
}
}
var vm = new Vue({
mixins: [mixin],
methods: {
bar: function () {
console.log('bar')
},
conflicting: function () {
console.log('from self')
}
}
})
vm.foo() // -> "foo"
vm.bar() // -> "bar"
vm.conflicting() // -> "from self"
|
注意 Vue.extend() 使用同样的合并策略。
全局混合
也可以全局注册混合。小心使用!一旦全局注册混合,它会影响所有之后创建的 Vue 实例。如果使用恰当,可以为自定义选项注入处理逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 为 `myOption` 自定义选项注入一个处理器
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// -> "hello!"
|
慎用全局混合,因为它影响到每个创建的 Vue 实例,包括第三方组件。在大多数情况下,它应当只用于自定义选项,就像上面示例一样。
自定义选项合并策略
在合并自定义选项时,默认的合并策略是简单地覆盖已有值。如果想用自定义逻辑合并自定义选项,则向 Vue.config.optionMergeStrategies 添加一个函数:
1
2
3
|
Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
// 返回 mergedVal
}
|
对于多数值为对象的选项,可以简单地使用 methods 所用的合并策略:
1
2
|
var strategies = Vue.config.optionMergeStrategies
strategies.myOption = strategies.methods
|
插件
插件通常会为 Vue 添加全局功能。插件的范围没有限制——通常是下面几种:
- 添加全局方法或属性,如 vue-element
- 添加全局资源:指令/过滤器/过渡等,如 vue-touch
- 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
- 一个库,提供自己的 API,同时提供上面提到的一个或多个功能,如 vue-router
Vue.js 的插件应当有一个公开方法 install。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:
1
2
3
4
5
6
7
8
|
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或属性
Vue.myGlobalMethod = ...
// 2. 添加全局资源
Vue.directive('my-directive', {})
// 3. 添加实例方法
Vue.prototype.$myMethod = ...
}
|
使用插件
通过 Vue.use() 全局方法使用插件:
1
2
|
// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
|
也可以传入一个选项对象:
1
|
Vue.use(MyPlugin, { someOption: true })
|
一些插件,如 vue-router,如果 Vue 是全局变量则自动调用 Vue.use()。不过在模块环境中应当始终显式调用 Vue.use():
1
2
3
4
5
6
|
// 通过 Browserify 或 Webpack 使用 CommonJS 兼容模块
var Vue = require('vue')
var VueRouter = require('vue-router')
// 不要忘了调用此方法
Vue.use(VueRouter)
|
已有插件&工具
-
vue-router:Vue.js 官方路由。与 Vue.js 内核深度整合,让构建单页应用易如反掌。
-
vue-resource:通过 XMLHttpRequest 或 JSONP 发起请求并处理响应。
-
vue-async-data:异步加载数据插件。
-
vue-validator:表单验证插件。
-
vue-devtools:Chrome 开发者工具扩展,用于调试 Vue.js 应用。
-
vue-touch:使用 Hammer.js 添加触摸手势指令。
-
vue-element:使用 Vue.js 注册自定义元素。
-
vue-animated-list:方便的为 v-for 渲染的列表添加动画。
- 用户贡献的工具
vue使用心得
$mount
使vue实例挂载到元素上。
new Vue 中的component和template
component用来扩展HTML元素,封装可重用的代码。
在使用component时,需要注册组件构造器。
1
2
3
4
|
var App = Vue.extend({
//选项...
})
Vue.component('app', App);
|
使用component时,有两种方法:
-
在html中直接使用。此时需要有上面的注册步骤
1
2
3
|
<div id="example">
<app></app>
</div>
|
-
在new Vue初始化中使用。此时不需要上面的注册步骤
1
2
3
4
5
|
new Vue({
el: '#example',
template: '<App/>',
components: {App}
})
|
插件的使用
使用插件时,需要调用Vue.use()
另外插件的使用是直接插入到new Vue 的定义中。
1
2
3
4
5
6
7
8
9
10
|
import VueRouter from 'vue-router'
Vue.use(VueRouter);
var router = new VueRouter({
mode: 'history',
base: __dirname,
routes: routerConfig
});
new Vue({
router
});
|
.vue 文件
.vue文件包含三种类型的标签: template, script, style
1
2
3
4
5
6
7
8
9
|
<template><div>121212</div></template>
<script>
import App from './app'
</script>
<style>
div{width:100px}
</style>
|
router中文件属性定义
由于只有主文件中有new Vue的定义,因此原本有些因此在new中定义的属性和方法,需要到component中去定义。比如 data,computed,ready等。
其中data需要通过函数返回json来实现。
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
|
// header.vue
<template id="">
<div class="header" v-bind:id="headerId">
<h2>header {{msg}}<span v-html="html_msg"></span>{{1 + 1}}</h2>
<div>{{msg | myfilter | myfilter}}</div>
<div><span v-if="bool.t">true</span><span v-if="bool.f">if</span><span v-else>else</span></div>
<div><a v-bind:href.literal="link">链接bind</a><a :href="link">链接</a><a v-on:click="myclick">click on</a><a @click="myclick">click</a></div>
<div>{{computedMsg}}</div>
</div>
</template>
<script>
export default {
data () {
return {
msg: 'js Msg',
html_msg: '<em>htmlmsg</em>',
headerId: 'topHeader',
bool: {
t: true, f: false
},
link: 'baidu.com',
myclick () {
window.alert('click')
}
}
},
computed: {
computedMsg () {
return this.msg + 'computed'
}
}
}
</script>
|