Vue 基础
本文于1049天之前发表,文中内容可能已经过时。
介绍
渐进式框架,核心库只关注视图层
声明式渲染
1 | <div id="app"> |
数据和DOM绑定在一起,即所有元素都是响应式的
指令:带有前缀v-,以表示他们是Vue提供的特殊属性
条件与循环
1 | <div id="app-3"> |
这意味着不仅可以绑定DOM到数据,也可以绑定DOM结构到数据。而且Vue在插入/更新/删除元素时自动采用过渡效果
还有比如v-for指令1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<div id="app-4">
<ol>
<li v-for="todo in todo">
{{todo.text}}
</li>
</ol>
</div>
var app4 = new Vue({
el:"#app-4",
data:{
todos: [
{text: "撒旦法地方"},
{text:"对方是发达"},
{text: "的方式"}
]
}
})
处理用户输入
1 | <div id="app-5"> |
能看得出,所有DOM操作都由Vue来处理,我们并没有触碰DOM
Vue还提供了v-model指令,他能轻松实现表单输入和应用状态之间的双向绑定1
2
3
4
5
6
7
8
9
10
11
12<div id="app-6">
<p>{{message}}</p>
<input v-model="message">
</div>
var app6 = new Vue({
el: '#app-6',
data: {
message: 'Hello Vue!'
}
})
组件化应用构建
它是一种抽象,类似积木一般来搭建大型应用
在Vue中,一个组件本质上是一个拥有预定义选项的一个Vue实例,在Vue中注册组件很简单1
2
3
4//定义名为todo-item的新组件
Vue.component('todo-item',{
template: '<li>这个是组件</li>'
})
现在构件组建模版1
2
3
4<ul>
<!--创建一个todo-item组件的实例-->
<todo-item></todo-item>
</ul>
来扩展下,将数据从父作用域传到子组件1
2
3
4
5
6
7Vue.component('todo-item',{
//todo-item组件现在接受一个“prop”
//类似于一个自定义属性
//这个属性名为todo
props:['todo'],
template:'<li>{{todo.text}}</li>'
})
然后,使用v-bind指令将todo传到每个重复的组件中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-7">
<ul>
<!--
现在为每个todo-item提供todo对象
todo对象是变量,即其内容是动态的
同时需要为每个组件提供一个‘key’
-->
<todo-item
v-for='item in groceryList'
v-bind:todo='item'
v-bind:key='item.id'>
</todo-item>
</ul>
</div>
Vue.component('todo-item',{
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:'辣酱'},
]
}
})
子单元通过props接口实现了与父单元很好的解耦
#与自定义元素的关系
能注意到,Vue组件非常类似于自定义元素————它是Web组件规范的一部分,因为Vue的组件语法部分参考了该规范,如Vue组件实现了Slot API与is特性,但还是有几个关键差别
- Web组件规范仍处草案阶段,尚无浏览器原生实现,而Vue组件不需任何补丁,且所有支持Vue的浏览器下表现一致,必要时,Vue组件也可以包装于原生自定义元素之内
- Vue组件提供了纯自定义元素所不具备的一些重要功能,最突出的是跨组件数据流,自定义事件通信以及构建工具集成
Vue实例
虽然没有完全遵循MVVM模型,但无疑受到了它的启发
创建时可以传入一个选项对象,一个Vue应用由一个通过new Vue创建的根Vue实例,以及可选的嵌套的、可复用的组件树组成
数据与方法
实例在创建时,会向Vue的响应式系统中加入其data对象中的所有属性。当这些属性值发生改变时,视图层就会发生响应,即匹配更新为新的值。当数据层改变时视图层会对应进行重渲染。
不过要注意,只有当实例被创建的那一个data中存在的属性是响应的,之后在data中添加任何新属性都无法触发视图层更新,所以如果想再晚些时候需要辣么一个属性,记得设置初始值。
除了data属性,Vue还暴露了一些实例的属性和方法,他们都有前缀$,如:1
2vm.$data===data
vm.$el===document.getElementById("app-7")
实例生命周期
每个实例被创建时都会有一系列的初始化过程,例如需要设置数据监听、编译模版、挂载实例DOM、在数据变化时更新DOM等。
同时该过程也会运行一些叫做生命周期钩子的函数,给予用户机会在一些特定的场景下添加他们自己的代码
如:create钩子就是用来在一个实例创建之后执行的代码1
2
3
4
5
6
7
8
9
10new Vue({
data:{
a:1
},
created:function(){
//this 指向vm实例
console.log(this.a)
}
})
// -> 1
不过还有些钩子,在实例生命周期的不同场景下调用,如mounted、updated、destroyed。
钩子的this指向调用他的Vue实例
不要在选项属性或回调上使用箭头函数,比如
created:() => console.log(this.a)或
vm.$watch(‘a’,newVlaue => this.myMethod())
因为箭头函数是和父级上下文绑定在一起的,this不会是你所预期的Vue实例,经常导致一些错误。
在此写下来,以便看到时知道那里犯了错
- Uncaught TypeError: Cannot read property of undefined
- Uncaught TypeError: this.myMethod is not a function
生命周期图示
模版语法
Vue.js使用基于HTML的模版语法,允许声明式地将DOM绑定至底层Vue实例的数据。所有模版都是合法html,所以能被浏览器和HTML解析器解析
底层实现上,Vue将模版编译成虚拟DOM渲染函数,结合响应系统,在应用状态改变时,Vue能智能地算出重渲染组建最小的代价并应用到DOM操作上
如果熟悉虚拟DOM并偏爱JS的原始力量,可以不用模版,直接写渲染(render)函数,使用可选的JSX语法
插值
大括号语法不能作用在HTML特性上,请用v-bind指令1
<div v-bind:id="newId"></div>
这同样适用于布尔类特性,如果求值结果是falsy(指的是所有能被转换成false的值,包括false)的值,则该特性将会被删除
大括号里面可以写JS代码,但只能包含单个表达式,如三元表达式,而流控制是不会生效的
模版表达式只能访问全局变量的一个白名单,如Math和Date,不应该在模版表达式中试图访问用户定义的全局变量
指令
带有v-前缀的特殊属性。
指令属性的值预期是单个JS表达式(v-for例外)
指令的职责是,当表达式的值改变,将其连带影响响应式地作用于DOM
#参数
一个指令能接受一个参数,在指令名称后用“:”表示,如v-bind指令可用于响应式地更新HTML属性1
<a v-bind:href="url">...</a>
href 是参数,告知v-bind指令将该元素的href属性与表达式url的值绑定
再比如v-on指令,它用于监听DOM事件:1
<a v-on:click="dosomething">...</a>
这时,指令的参数是监听的事件名
#修饰符
以半角句号“.”指明的特殊后缀,用于指出一个指令应该以特殊方式绑定
例如,.prevent修饰符告诉v-on指令对于出发的事件调用event.preventDefault():1
<form v-on:submit.prevent="onSubmit">...</form>
计算属性和观察者
计算属性
对于任何复杂逻辑,都应当使用计算属性
来颗栗子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<div id="example">
<p>Original message:{{message}}</p>
<p>Computed reversed message:{{reversedMessage}}</p>
</div>
var vm = new Vue({
el:'#example',
data:{
message: 'Hello'
},
computed:{
//计算属性的getter
reversedMessage:function(){
//this 指向vm实例
return this.message.split('').reverse().join('')
}
}
})
上栗声明了个计算属性reversedMessage,我们提供的函数将用作属性vm.reversedMessage的getter函数1
2
3console.log(vm.reversedMessage) //'olleH'
vm.message='Goodbye'
console.log(vm.reversedMessage) //'eybdooG'
Vue知道vm.reversedMessage依赖于vm.message,所以vm.message一旦变化,所有依赖vm.reversedMessage的绑定也会更新。而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的getter函数是没有副作用的,这是他更易于测试和理解
#计算属性缓存vs方法
其实上例也可以通过在表达式中调用方法来达到同样的效果:1
2
3
4
5
6
7
8<p>Reversed message:{{reversedMessage()}}</p>
//在组件中
methods:{
reversedMessage:function(){
return this.message.split('').reverse().join('')
}
}
这种在method中定义方法,其最终结果确实和计算属性一样。蓝鹅,不同的是计算属性是基于他们的依赖进行缓存的。计算属性只有在与它的相关依赖发生改变时才会重新求值。这意味着只要message没变,怎么访问这个计算属性都不会再算一遍,会立即返回之前计算好的的结果
而这导致了个有趣的情况:1
2
3
4
5computed:{
now:function(){
return Date.now()
}
}
on属性将不再更新,因为Date.now()不是响应式依赖
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数
为什么需要缓存?如果计算量花销很大,如果没有缓存会不可避免的多次执行计算
#计算属性vs侦听属性
Vue提供了更通用的方式来观察和响应Vue实例上的数据变动:侦听属性
当你有一些数据需要随着其他数据变动而变动时,会很容易滥用watch ————特别是有AngularJS经验的童鞋
而通常更好的方法是使用计算属性而不是命令式的watch回调
侦听器
当需要在数据变化时执行异步或开销较大的操作时,可以用watch来代替计算属性
Class与Style绑定
因为它们都是属性,所以可以用v-bind来处理:只需要通过表达式计算出字符串结果即可.
不过字符串拼接既麻烦且易错,因此在将v-bind用于class和style时,Vue.js做了专门的增强–表达式结果的类型除了是字符串外,还可以是对象或数组
绑定HTML Class
#对象语法
可以传给v-bind:class一个对象,用来动态切换class:1
<div v-bind:class="{active:isActive}"></div>
上面的语法表示active这个class取决于isActive这个属性是否为true.
可以在对象中传入更多属性,该指令也可以与普通的class属性并存,如下:1
<div class="app-1" v-bind:class="{active:isActive,test:isTest}"></div>
三元表达式也可以用在这
数组也可以用在这
#用在组件上
不会覆盖掉原有的class,比如:1
2
3Vue.component('my-component',{
template:'<p class="foo bar"></p>'
})
然后在调用插件的时候添加一些class:1
<my-component class="baz boo"></my-component>
HTML将被渲染为:1
<p class="foo bar baz boo">Hi</p>
绑定内联样式
#对象语法
v-bind:style的对象语法看着很像CSS,但其实是个JS对象.CSS属性名可以用驼峰或横短线分隔:1
2
3
4
5
6
7<div v-bind:style="{width:activeWidth+'px',color:activeColor}"></div>
data:{
activeWidth:299,
activeColor:'orange'
}
直接绑个样式对象通常更好,会更清晰:1
2
3
4
5
6
7
8
9<div v-bind:style="styleObject"></div>
data:{
styleObject:{
color:'red',
fontSize:'12px'
}
}
v-bind:style在添加需要前缀的CSS属性时会自己添上
条件渲染
#在元素上使用v-if条件渲染分组
就是当作块儿用的,不会出现在渲染结果上
#v-else
1 | <div v-if="Math.random()>0.5"> |
v-else元素必须紧跟在带v-if或v-else-if的后面
#v-else-if
2.1.0新增,顾名思义
#用key管理可复用的元素
Vue会尽可能高效地渲染元素,通常会复用已有元素而不是从头渲染,例如input框中的文本,如果Vue觉得这个input不需要在渲染,那就留着.不过有些需要中这个不应该存在,这是就需要绑定一个key,让Vue知道这个input不是同一个,需要重新渲染
v-show
带有v-show的元素始终会被渲染并保留在DOM中,其实就是切换display属性
不过要注意v-show不支持元素,也不支持v-else
v-if vs v-show
v-if是真正的条件渲染,在切换过程中条件块中的事件监听器和自组件会被销毁和重建
v-if也是惰性的:如果初始条件为假则啥也不做,直到条件变成真才开始渲染
相比之下,v-show就简单得多,元素总是会被渲染
一般来说,v-if有更高的切换开销,而v-show有更高的初始渲染开销.
因此如果需要频繁切换,则使用v-show;
如果运行时条件很少改变,则使用v-if较好
v-if 与 v-for一起使用
v-for具有比v-if更高的优先级
列表渲染
在v-for中,我们拥有对父作用域属性的完全访问权限.v-for还支持一个可选的第二个参数作为当前项的索引1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<ul id="example-2">
<li v-for="(item,index) in items">
{{parentMessage}}-{{index}}-{{item.message}}
</li>
</ul>
var example2 = nwe Vue({
el:"#example-2",
data:{
parentMessage:'Parent',
items:[
{message:'Foo'},
{message:'Bar'}
]
}
})
结果:
Parent-0-Foo
Parent-1-Bar
也可以用of替代in作为分隔符,毕竟这是最接近JS迭代器的语法
一个对象的v-for
1 | <ul id="v-for-object" class="demo"> |
在遍历对象时,是按Obejct.keys()的结果遍历,不能保证结果在不同的JS引擎下是一致的
key
由于就地复用策略,如果数据项的顺序被改变,Vue却不会迁就去改变DOM元素的顺序,而是简单复用此处每个元素,且确保它在特定索引下显示已被渲染过的每个元素
该默认模式是高效的,但只适用于不依赖子组件状态或临时DOM状态的列表渲染输出
为了给个提示,以便Vue能跟踪每个节点的身份,从而重用和重排现有元素,需要提供唯一性的key属性
尽量在使用v-for时提供key,除非遍历输出的DOM内容非常简单
因为它是Vue识别节点的一个通用机制,key并不与v-for特别关联,key还具有其他用途
数组更新检测
#变异方法
Vue有观察数组的变异方法,例如push() pop()等方法,都会触发视图层的更新
#替换数组
变异方法(mutation method)顾名思义,会改变被这些方法调用的原始数组.相比之下,也有非变异方法,如filter(),concat(),slice().这些不会改变原始数组,但总是返回一个新数组.当使用非变异方法时,可以用新数组替换旧数组来达到触发视图层改变
#注意事项
由于JS的限制,Vue不能检测以下变动的数组:
- 当利用索引直接设置一个项时,比如
1
2
3
42. 当修改数组的长度时,比如```vm.items.length = newLength```
第一类问题,有两个方法可以解决:
比如设置vm.items[indexOfItem]=newValue时
Vue.set(example1.items,indexOfItem,newValue)
//
example1.items.splice(indexOfItem,1,newValue)1
第二类问题,可以用splice:
example1.items.splice(newLength)1
2
3
4
5
6
7
8
9
10
11
12
13
14
### 对象更改检测注意事项
由于JS限制,Vue不能检测对象属性的添加或删除
这个之前就提过,实例创建时data所有属性会被设置为响应式的,再之后就不会识别
当然还是有办法解决的,可以使用Vue.set(object,key,value)的方法向嵌套对象添加响应式属性
### 一段取值范围的v-for
v-for可以取整数,如"n in 10",将重复多次模版.如日历表格生成时可用这个
### v-for on a <template>
类似于v-if v-for也支持它
### v-for with v-if
当处于同一节点,v-for优先级更高,这意味着v-if将分别重复运行与每个v-for循环中.
1 | 上述代码只传递了为complete的todo |
No todos left!
1 |
|
1
2至于不自动将item注入到组件里的原因是,这会使得组件与v=for的运作紧密耦合.而明确组件数据的来源能够使组件在其他场合重复使用
下面是一个简单的todo list的完成例子:
1 | >注意这里的is="todo-item"属性,这种做法在使用DOM模版时是十分必要的,因为在ul元素内只有li元素会被看作有效内容,这样做实现的效果与<todo-item>相同,但是可以避开一些潜在的浏览器解析错误 |
Vue.component(‘todo-item’,{
template:’\
Vue 基础\
\
props:[‘title’]
})
new Vue({
el:’#toto-list-example’,
data:{
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:function(){
this.todos.push({
id:this.nextTodoId++,
title:this.newTodoText
})
this.newTodoText=’’
}
}
})
1 |
|
1 | >使用修饰符时,顺序很重要;相应的代码会以相应顺序产生.因此如@click.prevent.self会阻止所有的点击,而@click.self.prevent只会阻止元素上的点击 |
1 | 不像其它只能对原生DOM事件起作用的修饰符,.once修饰符可以用在自定义的组件事件上 |
Checked names:
new Vue({
el:”#example-3”,
data:{
checkedNames:[]
}
})1
2
3
#### #选择列表
单选列表:
Selected:
new Vue({
el:’…’,
data:{
selsected:’’
}
})1
2
3
4
5
6
7
8>之所以写这个,是有个注意项:如果v-model表达初始的值不匹配任何选项,<select>就会以"未选中"的状态渲染.
在iOS中,这会使用户无法选择第一个选项,因为这样的情况下,iOS不会引发change事件.因为要提供disabled选项
### 修饰符
#### #.lazy
默认情况下,v-model在input事件中同步输入框的值与数据.而如果添加lazy,就转变为在change事件中同步
#### #.number
如果项将输入值转为Number类型,这个很有用
1
2#### #.trim
过滤输入的首尾空格
`