logo头像

王者风范 自由洒脱

Vue 基础

本文于1049天之前发表,文中内容可能已经过时。

介绍

渐进式框架,核心库只关注视图层

声明式渲染

1
2
3
4
5
6
7
8
9
10
11
<div id="app">
{{message}}
</div>


var app = new Vue({
el:'#app',
data:{
message : 'Hello Vue!'
}
})

数据和DOM绑定在一起,即所有元素都是响应式的
指令:带有前缀v-,以表示他们是Vue提供的特殊属性

条件与循环

1
2
3
4
5
6
7
8
9
10
11
<div id="app-3">
<p v-if="seen">你看到了</p>
</div>


var app3 = new Vue({
el: '#app-3',
data: {
seen: true
}
})

这意味着不仅可以绑定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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="app-5">
<p>{{message}}</p>
<button v-on:click="reverseMessage">逆转消息</button>
</div>


var app5=new Vue({
el:'#app-5',
data:{
message: "Hello Vue.js!"
},
methods:{
reverseMessage:function(){
this.message = this.message.split('').reverse().join('');
}
}
})

能看得出,所有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
7
Vue.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特性,但还是有几个关键差别

  1. Web组件规范仍处草案阶段,尚无浏览器原生实现,而Vue组件不需任何补丁,且所有支持Vue的浏览器下表现一致,必要时,Vue组件也可以包装于原生自定义元素之内
  2. Vue组件提供了纯自定义元素所不具备的一些重要功能,最突出的是跨组件数据流,自定义事件通信以及构建工具集成

Vue实例

虽然没有完全遵循MVVM模型,但无疑受到了它的启发
创建时可以传入一个选项对象,一个Vue应用由一个通过new Vue创建的根Vue实例,以及可选的嵌套的、可复用的组件树组成

数据与方法

实例在创建时,会向Vue的响应式系统中加入其data对象中的所有属性。当这些属性值发生改变时,视图层就会发生响应,即匹配更新为新的值。当数据层改变时视图层会对应进行重渲染。
不过要注意,只有当实例被创建的那一个data中存在的属性是响应的,之后在data中添加任何新属性都无法触发视图层更新,所以如果想再晚些时候需要辣么一个属性,记得设置初始值。
除了data属性,Vue还暴露了一些实例的属性和方法,他们都有前缀$,如:

1
2
vm.$data===data
vm.$el===document.getElementById("app-7")

实例生命周期

每个实例被创建时都会有一系列的初始化过程,例如需要设置数据监听、编译模版、挂载实例DOM、在数据变化时更新DOM等。
同时该过程也会运行一些叫做生命周期钩子的函数,给予用户机会在一些特定的场景下添加他们自己的代码
如:create钩子就是用来在一个实例创建之后执行的代码

1
2
3
4
5
6
7
8
9
10
new 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实例,经常导致一些错误。
在此写下来,以便看到时知道那里犯了错

  1. Uncaught TypeError: Cannot read property of undefined
  2. Uncaught TypeError: this.myMethod is not a function

生命周期图示

image

模版语法

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
3
console.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
5
computed:{
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
3
Vue.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-else

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

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


new Vue({
el:"#v-for-object",
data:{
object:{
firstName: 'John',
lastName: 'Doe',
age: 30
}
}
})

在遍历对象时,是按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. 当利用索引直接设置一个项时,比如
    1
    2
    3
    4
    2. 当修改数组的长度时,比如```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
    2
    上述代码只传递了为complete的todo  
    如果目的在于有条件地跳过循环的执行,可以将v-if至于外层元素或<template>上,如:





    No todos left!


    1
    2
    3
    4

    ### 一个组件的v-for
    在自定义组件中,可以像普通元素一样用v-for
    然而任何数据都不会自动传递到组件里,因为组件有自己独立的作用域.为了把迭代数据传递到组件里,要用props:



    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14


    ## 事件处理

    用v-on指令监听DOM事件来触发JS代码

    ### 事件修饰符
    事件处理中阻止冒泡或阻止默认是很常见的需求.尽管methods中可以很容易实现,但更好的方式是:methods只负责纯粹的数据逻辑,而不是去处理DOM事件细节
    为此Vue为v-on提供了**事件修饰符**
    - .stop
    - .prevent
    - .capture
    - .self
    - .once














    1
    2
    3
    >使用修饰符时,顺序很重要;相应的代码会以相应顺序产生.因此如@click.prevent.self会阻止所有的点击,而@click.self.prevent只会阻止元素上的点击  

    2.1.4新增了.once修饰符,表示事件将只会触发一次



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    不像其它只能对原生DOM事件起作用的修饰符,.once修饰符可以用在自定义的组件事件上  
    #### #鼠标按钮修饰符
    2.1.0新增
    - .left
    - .right
    - .middle

    用以监听特定的滑鼠按键

    ### 为什么在HTML中监听事件?
    这并非违背了关注点分离的传统理念,因为所有的Vue.js事件处理方法和表达式都严格绑定在当前视图的ViewModel上,他不会导致任何维护上的困难
    使用v-on的好处:
    - 和DOM完全解耦,更易调试
    - 当一个VM被销毁时,所有事件处理器都会自动被删除,因此无需担心如何手动清理它们



    ## 表单输入绑定

    ### 基础用法
    v-model本质上不过是语法糖,负责监听用户的输入事件以更新数据

    #### #复选框











    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
    过滤输入的首尾空格


    `