遇见Vue.js
mvx模式
mvc模式:最开始由后端出现,目的是让前端的页面和数据的获取解耦,页面和数据可以各自变化。
mvp模式:mvc的改进版本,将控制器的权限进一步扩大,让其负责所有的业务逻辑,也叫Presenter,页面只和控制器通过接口交互,不通过后端直接将数据渲染到页面上,这样就可以让前端作为单独的服务部署。
mvvm模式:这个其实就是将控制器替换成VM(ViewModel),ViewModel通过数据绑定,让view的变化通知ViewModel,而ViewModel的属性变化也会同步到view上,原理其实就是观察者模式,不理解的可以看设计模式。
Vue.js是什么
vue.js不是一个框架,严格意义上来说是一个提供了web的视图界面的库,提供了非常方便的数据绑定和灵活的组件系统。
特点:轻量化;数据绑定;简单的内置指令就可以改变DOM元素;集成其他组件库非常方便。
其他mvvm框架:angular.js;react.js;reactive.js;Backbone.js;Riot。
使用Vue.js
引入库方式:我们可以通过传统的script,在脚本里引入CDN文件;如果项目基于npm,bower包管理工具则可以使用命令。
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
npm i vue --save-dev
bower i vue --save-dev
我们引入库文件后就能直接使用了,页面元素的写法其实都和html一样的写法,只是vue添加了内置的指令用来简便使用。
数据绑定
数据绑定是将数据和视图通过观察者模式互相通知,一旦对方发生变化自己这边立马收到消息改变。
语法
插值
插值使用两个大括号来表示‘{{xxx}}’,括号里面写的是数据对象名,当数据对象里的值改变时,页面里的插值也会相应改变;如果我们只需要渲染一次,后续的变化不用管,则可以使用‘{{*xxx}}’;插值里面属性的会被当成文本字符串来显示,如果想要让插值里面的属性当html片段表示,可以使用三个大括号‘{{{xxx}}}’。
<div>{{text}}</div> // 脚本里有个数据对象叫text,text:'你好'
<div>{{{text}}}</div> // 脚本里有个数据对象叫text,text:'<span>hello</span>'
表达式
表达式就是各种变量,数值,运算的综合表示,插值里也支持JS表达式和过滤器的用法,但是不支持直接的语法语句。过滤器就类似与linux的管道,使用‘|’符号,允许多个过滤器串联,本质就是使用js函数过滤数据,内置了多个过滤器。
<div>{{text/100}}</div> // 值就是text数据除以100
<div>{{text?1:0}}</div> // 三元表达式
<div>{{text.split('', '')}}</div> // 函数表达式
<div>{{text | filter a b | toUpperCase}} // 过滤器使用,filter是JS函数,a和b是参数
指令
也就是带有v-前缀的属性,值限定为绑定表达式,也就是js表达式和过滤器,作用就是当表达式的值变化,dom元素也将变化。
<div v-if="show">hello</div> // 当show值为true时,块元素才显示
分隔符
插值的符号一般都是使用‘{{}}’,如果我们想要自己定义一个符号,可以再Vue.config对象里修改,该对象包含了Vue.js的所有全局配置,该对象需要在Vue实例化前修改。
Vue.config.delimiters = ['<lb', 'lb>'] // 插值字符串的表示符号
Vue.config.unsafeDelimiters = ['<lb', 'lb>'] // 插值html片段的表示符号
指令
基础指令有很多,如下图所示:
内部指令
v-if:根据表达式在DOM中生成或者移除一个元素,生成的话是将元素的克隆插入,如果想要对连续多个元素使用同样的表达式控制,可以使用< template >元素作为包装元素,在最终渲染里不会产生该元素。
<template v-if="show">
<div>hello</div>
<span>hello</span>
</template>
v-show:根据表达式在DOM中显示或者隐藏一个元素,原理是给元素添加内联样式style="display:none"实现,不支持< template >语法,vue会有局部的编译或者卸载的过程,使用v-if的时候,会真实的将元素重建和销毁,事件监听器和子组件也会重新建立,v-if是惰性创建,虽然编译会缓存起来,但是频繁的切换会影响效率。
v-else:和v-if或者v-show配套使用的,但是当v-show用在组件上面时最好不要使用v-else,会出现意外问题,最好使用v-show反向表达式来替换v-else。
v-model:将input,select,text,checkbox,radio表单空间元素创建双向数据绑定,v-model是一个语法糖,在元素事件里更新数据而已,后面可以添加参数,number(将用户输入转为number类型),lazy(将数据同步的事件触发改到change事件里),debounce(设置数据同步的最小延迟时间)。
<input v-model="msg" lazy />
v-for:基于源数据重复渲染元素,可以使用$index获取对应的数组索引,v-for会有一个作用域,需要在指定属性传递数据,否则组件内会获取不到数据,对于绑定的数组,我们应该避免直接设置数据,这些变化会让vue检测不到,可以使用 $set, $remove方法,也可以直接使用filter,concat,slice方法;有时候我们需要直接替换整个数组导致整个列表重新渲染,这时候我们可以通过给数组添加唯一id,然后通过track-by属性绑定id,之后如果替换,vue会检测该id如果存在了就直接复用元素,不需要重新渲染,不过这会导致dom节点不映射数组元素顺序的改变,不能同步input元素的值,以及子组件的私有状态;由于js的限制,数组通过索引设置元素,或者直接修改数组长度操作,vue是无法检测到变化的,可以通过 $set方法和替换空数组方案;支持< template >语法;支持遍历对象,可以通过 $key或者对象的属性名,或者给属性取别名直接使用key获取,对属性的增删也是无法检测变化的,需要 $add, $set, $delete方法。
<myitem v-for="item in items" :item="item" :index="$index">{{item.text}}</myitem>
<div v-for="item in items" track-by="uid"></div>
items:[{uid:'123'},{uid:'134'},...]
<div v-for="value in items">{{$key}} : {{value}}</div> // 遍历对象第一种方式
items:{uid:'123', name:'xxx', age: 15}
<div v-for="(key, value) in items" track-by="uid">{{key}} : {{value.msg}}</div> // 遍历对象第二种方式
items:{one:{msg:'111'}, two:{msg:'222'}}
<input v-model="searchText"> // 使用过滤器
<div v-for="item in items | filterBy searchText in 'name'" >{{item.name}}</div>
v-text:可以同步更新元素的textContent内容,也就是文本内容。
v-html:在内部插入html片段,会导致数据绑定忽略,和三个大括号插值相同。
v-bind:用来响应更新html特性,将attribute或者组件prop动态绑定到表达式;v-bind可以简化为一个’:'表示;当绑定到class或style时,可以使用数组或对象;没有参数的时候,可以绑定对象,对象里的属性都是元素的attribute;绑定prop的时候,必须子组件声明才行,可以用不同的修饰符指定不同的绑定类型(.sync,.once,.camel)。
<div v-bind:class="classA" ></div>
<div :class="[classA, {classB: isB, classC: isC}]" ></div>
<my-component :prop.sync="someThing" ></my-component>
v-on:用于绑定监听事件器的,事件类型由参数指定,参数就是v-on:click里的click;如果用在普通元素上,只能监听原生DOM事件,如果用在自定义组件里,可以监听子组件触发的事件;事件监听有两种方式,一种是方法处理器,一种是内联语句处理器,如果用内联语句,可以使用 $event 特殊变量将其传入方法里,也可以访问 $arguments属性,是一个数组包含了传给子组件的 $emit回调的参数;支持修饰符,.stop停止冒泡,.prevent阻止默认行为,.capture事件侦听器,.self绑定元素本身触发时才触发回调,.{keyCode|keyAlias}按特定键时才触发回调。
<button v-on:click="doThis"></button> // 方法处理器
<button v-on:click="doThis('hello', $event)"></button> // 内联语句处理器
<button @click="doThis"></button> // 方法处理器缩写
<button @click.stop="doThis"></button> // 修饰符
v-ref:在父组件上注册子组件的索引,可以通过 $refs对象访问子组件;可以和v-for同时使用,绑定对应数组或者对象看v-for的表达式里绑定的什么类型;因为html不区分大小写,变量名会全部转为小写,所以驼峰命令的话可以用’-'表示,例如:some-ref,访问的话就是someRef。
v-el:给DOM元素注册一个索引,可以通过 $els对象访问注册元素。
<div v-ref:some-ref ></div> // 注册元素
this.$els.someRef.textContent // 获取元素的文本内容
v-pre:编译时跳过当前元素和子元素,可以用来显示原始的标签内容,跳过大量没有指令的节点可以加快编译。
v-cloak:该指令会保持在元素上直到关联的实例结束编译,当某些渲染页面时,有可能会让用户先看到原始插值变量名,然后编译后才替换为值,这就可以通过该指令去隐藏未编译完成的元素。
[v-cloak] {
display: none;
}
<div v-cloak>{{msg}}</div>
自定义指令
基础
自定义指令提供了一种机制,将数据的变化映射为DOM行为,通过Vue.directive(id, definition)方法注册全局自定义指令,两个参数分别为指令名和定义对象,如果需要局部自定义指令则使用组件的directives选项。
钩子函数:vue提供了三个钩子函数,bind(只调用一次,在指令第一次绑定元素时调用),update(bind之后立即以初始值为参数调用,之后每次绑定值变化时调用),unbind(只调用一次,在指令解绑时调用)。
<div v-my-directive="value"></div>
Vue.directive('my-directive', {
bind: function(){
// 准备工作
},
update: function(newValue, oldValue){
// 值更新时的工作
},
unbind: function(){
// 清理工作
}
})
Vue.directive('my-directive', function(){ // 当只需要update方法时,可以使用函数替代定义对象
// 值更新时的工作
})
指令实例属性:所有的钩子函数都会被复制到实际的指令对象里,钩子函数里this指向这个指令对象,这个对象有一些有用的属性,el(绑定的元素),vm(指令的上下文ViewModel),expression(指令的表达式),arg(指令的参数),name(指令的名字),modifiers(一个对象,包含指令修饰符),descriptor(一个对象,包含指令的解析结果)。
对象字面量:如果指令需要多个值,可以传入一个js的对象字面量,例如v-my-directive=“{name: ‘xxx’, age:18}”。
字面修饰符:如果指令传入了字面修饰符,那么值会按照普通字符串处理,update只会调用一次,普通字符串不能响应数据变化,例如v-my-directive=“abc cbd def”。
元素指令:如果我们想让指令作为页面元素一样使用,可以使用下面例子的方式,该方式的创建方式和普通指令相似,但是该指令不能有参数和修饰符,而且vue遇到元素指令会跳过该元素及其子元素,只有元素指令本身才可以操作该元素和子元素。
Vue.elementDirective('my-directive', {
bind: function(){
// 准备工作
}
})
高级选项
params:元素指令可以在定义对象里设置一个params数组,该数组用于指定一个特性列表,vue编译器会自动提取绑定元素的这些特性;也可以使用动态的绑定数据,属性值会自动保持同步,也可以指定回调,当值变化时调用方法。
Vue.elementDirective('my-directive', {
params: ['abc'],
bind: function(){
// 准备工作
}
})
<my-directive abc="xxx"></my-directive> // 普通使用
Vue.elementDirective('my-directive', {
params: ['abc'],
paramWatchers: {
abc: function(val, oldVal){
console.log(val)
}
}
})
<my-directive :abc="someValue"></my-directive> // 动态属性使用
deep:当定义对象里传入的是一个对象,对象内部的属性变化时要触发update方法,需要在自定义指令里添加deep:true。
twoWay:指令想向vue实例写会数据,则需要在指令里添加twoWay:true,该选项允许在指令里使用this.set(value)。
acceptStatement:让指令接受内联语句,则需要在指令里添加acceptStatement:true,该选项允许在指令接受内联语句,将其当做传入一个函数来使用。
terminal:当vue编译DOM元素时,遇到terminal指令时会停止遍历该元素的后代元素,让该指令去接管编译这个元素和后代元素,v-if和v-for都是terminal指令,指令里添加terminal:true,而且需要使用FragmentFactory,比较麻烦,建议通读v-if和v-for源码。
priority:给指令设置一个优先级,普通指令默认是1000,terminal则是2000,同一个元素上优先级高的指令会比优先级低的先执行,同等级的会按元素的特性列表顺序执行,但是不同浏览器列表顺序可能不同,v-if和v-for一定是最高的优先级。
常见问题解析
v-on可以在同一个元素上绑定多个事件,但是只会绑定第一个事件,其他事件会被忽略。
vue实例里的el只是为实例提供挂载元素,可以是元素,可以是css选择符,也可以是返回html元素的函数。
计算属性
当我们在模板中绑定表达式,如果表达式过多,会导致模版臃肿,维护困难,可以使用计算属性来替代表达式。
计算属性是什么
计算属性是一个属性,当它依赖的属性发生变化的时候,它会自动更新并且将绑定的DOM元素同步更新。
var vm = new Vue({
computed: {
xxxAttr: function(){ // 相当于该属性的getter方法
return this.oneAttr+ this.twoAttr
}
}
})
var vm = new Vue({
computed: {
xxxAttr: {
get: function(){ // 当vm里面的oneAttr或者twoAttr属性变化时,会调用该方法更新xxxAttr属性
return this.oneAttr+ this.twoAttr
},
set: function(newVal){ // 设置xxxAttr值时,会调用该方法更新其他属性值
this.oneAttr = ‘hello’
this.twoAttr = 'world'
}
}
}
})
计算属性缓存
当我们使用计算属性时,最好不要在方法里执行大量耗时的操作,这会严重影响性能,而计算属性是只有在依赖属性变化的时候才会调用getter方法,里面使用了缓存的机制,但是我们要是想要获取实时的值,我们可以通过设置cache:false属性来实时获取信息。
var vm = new Vue({
computed: {
xxxAttr: {
cache: false,
get: function(){
return this.oneAttr+ this.twoAttr
}
}
}
})
常见问题
当我们使用计算属性的时候,如果我们使用计算属性的模版或者元素被移除时并且其他地方没有引用该计算属性时,不会调用getter方法。
当我们需要在单条数据上面使用计算属性,每个计算属性都属于自己的,改变不会影响其他元素的计算属性,这样可以使用v-repeat指令+自定义元素组件,在自定义元素组件里添加计算属性实现。
<div id="items">
<my-item v-repeat="items" inline-template>
<button>{{fulltext}}</button>
</my-item>
</div>
var items = [{num:1, text: 'aaa'}, {num:2, text: 'bbb'}]
var vm = Vue({
el: '#items',
data: {items: items},
components: {
'my-item': {
replace: true,
computed: {
fulltext: function(){
return 'item' + this.text
}
}
}
}
})
表单控件绑定
基本用法
在表单控件元素里使用v-model指令绑定数据,对于多选的控件,在option元素里使用v-for和v-bind指令绑定数据,在元素里如果有value,则赋值就是value里的属性值,如果没有则是默认布尔或者文本内容值。
值绑定
也就是在元素里添加:value属性绑定一个值,该值作为表单控件的实际值。
v-model修饰指令
lazy,number,debounce三个修饰指令,第一个是将数据同步放在change事件执行,第二个是将用户输入转为数字类型值,第三个是将数据同步操作延迟指定时间。
过滤器
过滤器在任何支持表达式的地方都可以使用,用法和linux管道一样,过滤器都是函数,会将上一个最终值作为当前过滤器的第一个参数,其他的参数空格分隔,带引号的作为字符串,不带的作为数据属性名。
内置过滤器
capitalize: 将表达式的首字母转为大写。
json: JSON.stringify()缩略版,返回由该函数处理过后的json结果。
limitBy: 限制数组的数量,第一个参数为个数,第二个参数是偏移量。
filterBy: 过滤数组的数据,返回新的数组,参数可以是字符串或者函数。
currency: 将数值类型的值表示成货币类型的输出。
自定义过滤器
语法
使用全局函数Vue.filter构造过滤器,其中区分为单参数,多参数,双向过滤器,动态参数。双向过滤器就是不管在模型同步视图,还是视图同步模型里,都可以执行。动态参数就是传入参数是模型的属性名。
Vue.filter('max', { // 创建一个max名字的过滤器, 双向过滤器
read: function(val){}, // 模型同步视图
write: function(newVal, oldVal){} // 视图同步模型
})
Vue.filter('reverse', function(val){ // 创建reverse过滤器
......
})
常见问题
使用过滤器后的 $index 索引值是根据新的数组或者表达式后的数据排序的。
自定义过滤器不只是可以在全局过滤器里定义,也可以在Vue实例的filters里写。
var vm = new Vue({
......,
filters: {reverse: function(val){}}
})
class和style绑定
绑定html class
使用v-bind指令绑定元素的class特性,能让元素样式根据条件变化,而绑定class和普通的class能同时存在于一个元素里,绑定表达式可以传入数据属性,对象和数组。
绑定内联样式
使用v-bind指令绑定元素的style特性,绑定一个样式对象比较好,也可以绑定一个计算属性,对于样式属性里如果有浏览器找不到的,vue会自动添加前缀使用。
<div v-bind:class="[ceshi1, ceshi2]"></div>
<div v-bind:class="[colorAttr, fontAttr]"></div>
过渡
过渡动画能让前端实现非常好看的效果,需要在元素上面使用transition特性,当触发DOM插入或者删除transition特性元素时,会先去找过渡钩子对象,找到了后就会在过渡不同阶段执行钩子函数,然后找css过渡或者动画,如果前两个都没有找到,则下一帧直接立即执行。
css过渡
过渡阶段的钩子函数有beforeEnter,enter,afterEnter,enterCancelled,beforeLeave,leave,afterLeave,leaveCancelled,stagger,enterStagger,leaveStagger。
<div v-if="isShow" transition="expand"></div> // 必须添加.expand-transition,.expand-enter,.expand-leave的css规则
Vue.transition('bounce', { // 自定义过渡类名
enterClass: 'bounceIn',
leaveClass: 'bounceOut',
type: 'animation', // 显示声明过渡类型,只会侦听animationed事件
css: true // 关闭css规则检测
})
如果当transition里没有指定值时,会使用默认的名字,v-transition,v-enter,v-leave。
自定义css类名,使用这个会覆盖默认的类名,这个搭配第三方动画库非常好用。
当我们想要侦听过渡何时结束时,有两个事件,transitioned和animationed事件,如果只是用其中一种vue可以自动推断类型,但是很多时候都是动画和过渡两个都会用的,这就需要我们显示声明类型。
过渡流程
删除元素时,会按照规则执行(新增类似):1.调用beforeLeave钩子;2.将v-leave的类名添加到元素上触发过渡;3.调用leave钩子;4.侦听transitioned事件执行;5.删除Dom元素和类名;6.调用afterLeave钩子。
当在进入过渡或者离开过渡时删除或者新增元素时,会调用enterCancelled或者leaveCancelled钩子。钩子函数内部this指向Vue实例,leave和enter可以有可选参数,显示控制过渡如何结束。
JS过渡
使用的就是纯js样式去控制元素,过渡创建和上面的css一样,都是使用Vue.tranistion去创建,但是钩子函数不能使用css相关的。
渐进过渡
使用transition特性和v-for指令可以实现渐进过渡的效果,再通过stagger,enter-stagger,leave-stagger特性给每个过渡项目添加延迟动画的时间,这样就能让每个过渡项目实现渐进过渡。
method
监听事件可以用js的方法实现,但是vue不提倡这么做,vue更提议用v-on指令去绑定事件监听。
内联方式
在元素的特性里使用v-on指令绑定一个事件处理器,该处理器可以使用一个js语句,也能使用方法名,在0.12版本后可以使用@作为缩写,而且多个事件必须分开绑定。
methods配置
当事件与方法绑定后,我们需要在vm里的methods属性里添加相应的方法,该方法可以在js外部被调用vm.xxx(),在方法里this指向Vue实例,支持event原生dom事件作为参数传入,当在自定义组件里使用时,也可以监听子组件的自定义事件。
$event的应用
当元素的内联语句需要访问原生的dom事件时,可以使用$event变量,也可以作为参数传入事件方法里。
修饰符
vue给v-on提供了四个修饰符,.prevent, .stop, .capture, .self四个,让js代码负责纯粹的数据逻辑,不需要关心dom事件细节,修饰符可以串联,也可以不绑定事件方法,只使用修饰符。
prevent :以前在事件处理器内需要调用event.preventDefault()方法,来阻止事件的默认行为,但是用这个修饰符就能完成。
stop :阻止事件冒泡event.stopPropagation()也是同样道理。
capture :添加事件侦听器时采用该修饰符,代表捕获模式。
self :表示只在该元素本身触发回调,子元素不触发。
按键修饰符
当我们需要监听键盘的按键事件,我们可以监听keyup事件,然后在事件后添加修饰符,该修饰符有别名也可以是keycode。
实例方法
实例属性
组件树访问
$parent:访问当前组件实例的父实例。
$root :访问当前组件树的根实例,如果没有父实例,则代表本身组件实例。
$children:访问当前组件的直接子组件实例。
$refs:用来访问v-ref指令的子组件。
DOM访问
$el:用来访问挂载当前组件实例的DOM元素。
$els:访问使用了v-el指令的DOM元素。
数据访问
$data:访问实例里的data属性的数据对象。
$options:访问组件实例化时初始化选项对象。
实例方法
除了删除和延迟只有一个参数回调方法,其他都有两个参数,第一个是插入的元素名或者选择器字符串,第二个是回调方法。
实例事件方法
$on:用来监听自定义事件。
$once:用来监听自定义事件,但是只触发一次。
$emit:用来触发事件。
$dispatch:用来派发事件,先当前实例触发,然后沿着父链一层一层往上,如果对应监听函数返回false就停止。
$broadcast:广播事件,遍历当前实例的 $children,如果对应监听函数返回false就停止。
$off:用来删除事件监听器。
组件
基础
全局注册
需要使用Vue.component()注册组件,里面两个参数,第一个是组件名,第二个就是组件构造函数(可以是函数或者对象)。
var component = Vue.extend({template: '<div>a custom component!</div>'})
Vue.component(‘xxx-component’, component) // 组件构造器写法
Vue.component(‘xxx-component’, {template: '<div>a custom component!</div>'}) // 使用对象写法,自动调用Vue.extend方法
局部注册
在不需要全局注册组件的时候,我们需要在实例里的components属性里去注册就可以。
var component = Vue.extend({
components: { 'xxxname' : {template: '<div>a custom component!</div>'}}
})
数据传递
由于实例的作用域都是独立的,所以当我们想要组件传递数据时,可以使用props,也可以使用v-bind指令动态绑定数据;当父组件的属性改变时会传导给子组件,但是反过来就不行,这会让数据流向不清晰,我们也可以使用修饰符来强行使用,.sync双向绑定,.once单次绑定;如果props是一个对象或者数组,不管是否是什么修饰符,那么在子组件里的修改都会影响父组件的状态。
可以对props里面的prop参数校验,通过 type: 属性可以指定参数是什么类型,例如:String,Number,Object,Function等。
propsData属性用来当组件初始化完成后覆盖props中的属性。
var component = Vue.extend({
props: ['xxxName'],
......
})
<child xxx-name="hello!"></child>
组件通信也就是通过 $dispatch 事件函数去向父组件传递数据。
slot分发内容,当父组件直接使用子组件的属性时,会出现问题,如果是用js方式创建模板使用,这样就不会有问题,因为作用域不同导致的。slot标签可以将模板替换到插槽标签位置,如果插槽标签内指定了名字的话,则会选择元素内有相对应slot特性的。
我们可以通过提供一个混合对象来使用,如果混合对象里有钩子函数和组合的对象钩子函数重名了,那么混合对象里的钩子函数会被先执行。
动态组件
多个组件可以使用同一个挂载点,然后在其中动态切换,需要在component元素里添加is特性就可以。
keep-alive :当切换出去的组件为了避免重新渲染,可以使用该特性加在component元素里,可以让组件缓存在内存中。
activate :这是组件里的钩子函数,在组件被切入前或者初始化前执行。
transition-mode :是component元素的特性,指定两个动态组件之间如何过渡,有in-out和out-in。
内联模板
使用内联模板inline-template特性,组件会把其内容当做模板来使用,而且优先级比普通template高。
片段实例:将模板内容替换为实例的挂载元素,所以推荐模板的顶级元素一个比较好。
生命周期
init:在实例初始化开始同步调用。
created:在实例创建完成后同步调用,数据绑定,计算属性,方法和事件回调已经完成建立,但是DOM还未开始编译, $el还不存在。
beforeComplie:在编译前调用。
compiled:已经完成编译,所有指令已经生效,数据变化会引起dom修改,但是不担保 $el 已经插入文档。
ready:结束编译和 $el 第一次插入文档中调用,必须是由Vue插入的才能触发ready函数。
attached:指令或者实例方法插入DOM元素时触发该函数( $appendTo ),直接操作vm. $el是不会触发的。
detached:指令或者实例方法删除DOM元素时触发该函数。
beforeDestroy:开始销毁实例前调用。
destroyed:实例被销毁过后调用。
组件开发
一般都是通过template标签和module.exports去导出组件,暂时没有弄懂。
表单校验
vue使用vue-validator插件去实现表单校验的功能。
安装使用
通过npm安装好了后,需要手动注册插件到Vue对象上,虽然该插件也会自动检测调用,但是在用webpack打包时,Vue对象不会暴露在全局window对象中,而是通过module.exports形式输出,所以需要手动注册。
var vue = require('vue');
var vueValidator = require('vue-validator');
vue.use(vueValidator);
使用方法如下:
校验结果会保存在 $validation里,校验结果结构是首先整体校验结果,其次就是单个字段校验结果。
验证器语法:
field 在2.0下时,是通过v-model绑定数据来确认要校验的属性名,而2.0之后就是通过v-validate指令来实现了,field 属性名采用’-'分隔,使用时用驼峰法。
校验规则定义
v-validate指令的值可以是数组字面量,对象字面量,组件实例数据属性名。当规则不需要额外参数时就用数组字面量,需要的话就用对象字面量,动态的话就用组件实例数据属性;v-validate指令与terminal指令不能同时使用。
内置规则
pattern源码就是使用的字符串的match()函数。
与v-model同时使用
v-validate指令会自动校验v-model绑定的值,结果是保存在v-validate后面指定的属性名里。
在vue组件实例上使用 $resetValidation()方法去重置校验结果。
自定义校验状态
校验器会根据不同的校验结果对应不同的class,如果我们需要自定义校验状态的class,就需要在validator元素或者使用v-validate指令的元素上使用才可以;添加classes属性即可。
如果想要在校验元素的包裹元素上添加校验状态class,需要在包裹元素上使用v-validate-class指令。
分组校验
使用validator标签包裹校验组,然后在validator标签里使用:groups="[‘passwordGroup’]"来指定一个分组,然后对里面需要校验的元素添加group特性指定所属分组,这样就必须要分组内所有的校验都通过了才算分组校验通过。
错误信息
对校验失败提示错误可以在校验规则里添加错误信息,例如:required: { rule: true, message: ‘required you name !!’} 。
所有的表单校验失败错误信息都会存储在errors数组里,通过 $validator-name.errors访问,数组里存储对象,对象有field和message属性。
我们可以利用分组校验,使用validator-errors标签,指定gooup分组名,绑定校验器标签名就可以显示单个分组的错误信息。
错误信息输出组件 :可以通过component组件和partial模板两种方式,component就是普通创建组件的方式,然后让validator-errors标签里:component=""绑定该组件即可;也可以将component特性改为partial模板也可以。
在js里动态设置错误信息,使用 $setValidationErrors来动态设置错误信息。
事件
vue-validator校验状态改变时,会触发相应事件,有valid,invalid,touched,dirty,modified事件,可以在单个校验元素上使用,也可以在整个表单校验器上添加。
在表单校验器上设置lazy属性可以延迟校验,需要手动调用 $activateValidator()方法才会初始化校验。
自定义验证器
使用Vue.validator()去全局注册,注册名可以在v-validate里的指定数组字符串里写。
在Vue实例对象里的validators属性里添加相应方法,可以局部注册,一样的使用方法,例:v-validate:name=“[‘validator-name’]”。
在自定义验证器方法里添加message属性就可以添加错误验证信息。
有些时候我们不需要在初始化的时候自动校验,可以添加特性initial="off"关闭,但是会在dom元素的input,blur,change事件触发,如果连这些都需要关闭,可以添加detect-change="off"关闭。
异步验证器
和自定义验证器一样的流程,只是方法返回值不一样,异步返回的是函数,签名为function(resolve, reject),返回Pormise对象,promise对象传入参数是函数,就是前一个函数。
验证器函数的上下文context
验证器函数里有两个属性可以使用,一个是vm,能获取当前验证器使用元素的vm实例,一个是el,暴露当前验证器所在DOM元素。
使用方式就是this.vm或者this.el 。
与服务端通信
vue本身没有提供与服务端交互的接口,但是通过插件的形式可以完成,vue-resource插件就通过jsonp技术实现异步加载,ajax也可以使用。
安装以及配置
安装有npm,bower以及手动编译三种方式,npm :npm install vue-resource --save-dev,然后在项目里引入该模块就可以,其他两个很简单,就不一一写上。
这是可以参数配置的属性,有三个地方可以配置,全局配置,组件实例化配置,调用时配置,这三个配置优先级依次提高,高优先级覆盖低优先级配置。这些参数配置都是请求配置,按需百度即可,不需要特别的专研。
基本http调用
不管是底层方法调用还是便捷方法调用,都返回一个Promise对象,根据Promise语法来注册成功或者失败回调。
Vue.http(option)和this.$http(option)是底层方法调用,不同点是回调方法里的this指向不同,(前者)全局指向的window,(后者)组件实例指向的组件实例本身。
// 实例化调用
new Vue({
ready: function(){
this.$http({
url: "/test",
method: "POST",
data: {
cat, 1
},
headers: { 'content-Type': 'x-www-form-urlencoded'}
}).then(fuction(response) {//成功回调}, function(response) {//失败回调})
}
});
像post,get,put这些方法的调用就是便捷方法调用,可以省去一些配置参数的设置。
// 便捷方法调用
this.$http.post('/test', data: {cat, 1},
{headers: { 'content-Type': 'x-www-form-urlencoded'}
).then(fuction(response) {//成功回调}, function(response) {//失败回调})
拦截器
可以在请求前或者响应后做出一些处理,有两种方式,一种是拦截器对象注册,一种是工厂函数注册,但是工厂函数注册其实就是返回了拦截器对象。
Vue.http.interceptors.push(fuction() {
return {
request: function(request){
// 处理请求
return request
},
response: function(response){
// 处理响应
return response
}
}
})
跨域AJAX
由于跨域问题现在基本很常见,但是需要浏览器支持XMLHttpRequest2技术,使用方式和原来一样,可以通过下面语句判断浏览器是否支持。
var cors = 'withCredentials' in new XMLHttpRequest()
服务端也需要在响应头的Access-Control-Allow-Origin设置前端地址,如果允许所以的异域请求,可以使用*。
Promise
Promise是返回对象,提供then,catch,finally方法去执行回调函数。
var promise = this.$http.post(...)
promise.then(...)
promise.catch(...)
注意: 在非简单请求的情况下,在浏览器发送post请求之前会额外的发送一个options类型的请求,当服务器正确响应了这个请求后才会发送真实请求,所以服务器端需要配置Access-Control-Allow-Methods: OPTIONS 。
路由与视图
安装以及使用
和请求接口一样,vue本身没有提供路由机制,需要安装官方的 vue-router 插件就可以实现。vue-router内部有检测window.Vue对象是否存在,如果存在会自动注册,如果不存在就需要手动注册,但是如果项目使用的CommonJS的规范的话,就必须手动注册,因为Vue对象不会暴露在window对象里,而是通过exports形式输出。
npm install vue-router // 安装命令
var VueRouter = require('vue-router')
Vue.use(VueRouter) // 手动注册插件对象
使用由于现在新的vue-router和旧版的vue-router使用方法不一样,所以旧版的就不写出来了,最新的使用方法请在前端开发–学习记录里查看。
v-link
v-link是一个指令,但是在最新的vue-router里可以使用router-link元素组件去使用,该指令也可以继续使用。
在v-link里使用的不是地址字符串而是对象时,可以有额外的属性:params,值为对象,用来设置路由里的参数键值对;query,值为对象,将对象里的键值对添加到路由路径后;replace,值为布尔值,表示是否产生历史记录;append,值为布尔值,表示是否将映射地址拼接到当前地址后;
router-view
这是一个元素组件,用来渲染匹配的组件,使用props传递数据,支持v-transition和transition-mode指令设置过渡动画,使用v-ref将组件注册到父组件this.$上,支持slot插槽。
路由实例
由于路由实例在新的版本里不适用了,新的是通过暴露的方法去实现各个功能,所以该模块看看就好,了解一下内部的属性。
路由的实例配置有:hashbang,布尔值,表示地址是否以hash模式显示;history,布尔值,表示是否history模式导航;saveScrollPosition,布尔值,表示可以后退到上一次浏览页面,需要history为true才可以使用;transitionOnLoad,布尔值,表示第一次加载router-view时是否有路由切换动画;suppressTransitionError,布尔值,表示切换钩子中产生的异常是否抛出;linkActiveClass,字符串值,表示元素激活时加在该元素上的类名,也就是激活时的样式;root,字符串,表示根路径,需要history为true才可以使用,当所有路径都匹配后,地址栏显示根路径+匹配路径。
路由实例方法:路由实例暴露了很多功能方法来实现用户的需求。
1. start(APP,el) ,该方法启动路由应用。
2. On(path, config) ,添加顶级路由配置。
3. Map(routerMap) ,批量定义路由映射规则。
4. go(path) ,导航到指定路径的路由。
5. redirect(redirectMap) ,定义全局重定向规则。
6. Alias(aliasMap) ,路由配置别名规则。
7. BeforeEach(hookFunction) ,全局注册前置钩子,多个按注册顺序调用,串行调用。
8. afterEach(hookFunction) ,全局注册后置钩子,多个按顺序调用,但是并行调用。
组件路由配置
每个路由对应一个组件,组件里面可以使用route字段,在里面注册各个阶段钩子函数,方便控制。
路由的三个阶段
路由切换有三个阶段,例如:当前路由/a/b/c,目标路由/a/g/h。
- 可重用阶段,检查当前组件树里是否存在可重用的组件(实际是看canReuse钩子),例如:例子中的a组件就可以重用。
- 验证阶段,检查b和c组件是否能够停用,g和h组件是否能够激活,实际通过canDeactivate和canActivate钩子函数判断,前一个重下往上检查,后一个是重上往下检查组件树。
- 激活阶段,当所以的检查钩子函数执行完毕并且中途没有终止切换,禁用当前组件,启用新组件。
各个阶段的钩子函数在执行时,如果有异步操作,切换会处于停止状态,直到异步操作被resolve。
钩子函数介绍
- canReuse :判断组件是否可重用,如果路径不一致该函数无效。
- canActivate :当一个组件要被切入时调用,接收transition对象,transition.next()去resolve钩子函数,transition.abort()取消切换。
- activate :当组件确认激活后,被创建完成,将要切入时调用,接收transition对象,该函数只有需要新激活的组件才会调用,重用的不会。
- data :组件activate钩子函数resolve后调用,加载和设置组件初始化数据,接收transition对象,在调用这个钩子函数之前,组件会有一个$loadingRouteData元属性,原本为true,该函数执行完毕会修改为false,可以用来作为组件切入加载数据动画的属性判断。
- canDeactivate :当组件要被切出时调用,接收transition对象。
- deactivate :当组件将要被禁用和移除时调用,接收transition对象。
路由匹配
每个组件都会被注入路由上下文对象,也就是 this.$route 。
动态片段: 在路径里使用:参数的形式就是动态片段,例如/a/:username,会匹配/a/xxx,也可以/a/test,而这个路径上的值会被当作参数存入 $route.params.username 里,对于/a/xxx/test就不能匹配,但是可以使用多个动态片段,都存入 $route.params 里。
全匹配片段: 在路径里使用 * 这个符合,* 是通配符,能匹配所有的路径,/a和/a/a都能匹配,但是优先匹配其他路由,当其他路由都不对时才匹配 * 路由。
具名路径: 就是路由规则里给路径写上name参数,这样在代码中使用的时候就可以直接使用名字,而不是写路径了。
路由对象属性:path :当前路由对象路径;params :路由路径匹配的键值对;query :路径上查询的参数;router :当前路由器的vue-router实例;name :路径名字;matched :当前路径匹配的所有片段的配置参数对象。
transition对象
该对象用来控制路由切换的时机,提供了to,from对象,next,abort,redirect函数。
嵌套路由
我们在使用router-view元素渲染我们的组件,我们当然也可以在组件里添加router-view,这样就形成了嵌套的关系,那么子路由就需要在路由规则里添加了,在父路由里添加subRoutes字段,里面写子路由的配置,写法和父路由一样,可以在子路由配置里添加 / 根路径代表当前默认路由。
动态加载路由
我们可以使用懒性加载的方式,当路由组件需要渲染的时候才渲染,而不是直接渲染。做法就是将路由规则里组件名改为函数加注册的形式。
常见问题解析
saveScrollPosition参数无效,这个在router里作用是切换页面时,视图停留在最后一次出现的位置,这个参数需要html5模式下和history参数为true时才有效。
监听不到查询参数,当地址只有参数变了,其他没变的情况下,vue-router会认为组件可重用,所以导致组件不会重新激活创建,所以可以在data钩子函数里去实现相应的监听。
vue-cli
这是vue工程项目脚手架工具,能够使用命令快速创建工程文件。
由于vue-cli时使用node命令创建的工具,所以需要node安装。
npm install -g vue-cli // 使用node安装工具
vue init webpack my-project // 快速构建基于webpack的工程项目
vue run xxx // xxx是package.json里面scripts命令名称,执行脚本命令
vue list // 列出可以构建的所有项目模板
vue-cli使用了很多npm第三方包,所以有比较完善的功能提供,当然也可以不适用vue-cli创建,详细请看前端开发–学习记录。
测试开发与调试
ESLint :用来编码规范风格检查的,可以避免低级bug。
Vue Syntax Highlight :vue文件内容高亮显示。
Snippets :代码自动补全工具,帮助加快开发效率。
WebStorm :一款不错的web页面编辑IDE工具。
VS Code :一款非常好的轻量级IDE工具,有很多可用插件,支持各种语言开发,本人非常爱用。
Vue Dvetools :官方提供的浏览器插件,调试工具,用来调试vue项目。
Scrat+Vue.js的化学反应
前端工程化
由于大型网站应用页面非常庞大,需要非常多的人去维护,所以需要通过软件工程的角度去思考前端开发,解决前端各种遇到的问题,例如多人协调,组件复用,项目管理,代码调优,调试部署等。
要做到前端工程化,需要做好几件事:1. 开发规范统一;2. js和css模块化开发,js的方案有amd/commonJS/umd/es 6 module,css就是less/sass/stylus预处理器的import/mixin特性支持下实现;3. 组件化开发,将页面拆分多个组件;4. 组件库的建立;5. 性能优化;6. 项目部署;7. 开发流程;8. 工程工具。
Scrat简介
Scrat是uc在百度的fis基础上二次开发的webapp模块化开发框架,功能非常强大,拥有模块化所需的所有能力。
scrat打包方法和引入模块方法加上vue编写页面就可以很好的融合两者特性。
Scrat安装及使用
使用npm命令下载,scrat自带脚手架工具,可以使用component组件方便开发。
npm install -g scrat
scrat init // 创建初始化工程
// 修改工程里的component.json文件,在dependencies里添加 "scrat-team/fastclick":"1.0.2" 代码
// 表示 github上 用户名/仓库名:版本号
scrat install // 安装依赖
代码使用,就是在index.html文件里引入scrat.js,然后在组件里通过fis的_inline方法将组件的tpl页面内容内嵌到组件的js文件里,这样就让js和模板分离了,可以通过require方式局部注册子组件。
Vue.js 2.0
比较1.x版本,该版本修改了部分api和使用灵活便捷,最重要的就是高性能Virtual DOM和流式服务端渲染。新增和废弃都有,所以没有必要一一列出,只介绍重要的,其他到官网或者源码里面查看就行了。
prop的双向和单向绑定(.sync, .once)被废弃,现在只允许单向绑定属性,子组件想要影响父组件必须通过事件分发,主要是为了解耦。
Vue里新增了一个render字段,是一个方法,可以将模板渲染存入,如果存在可以跳过模板解析,增加页面渲染速度。
废弃组件的事件派发和广播,通过全局事件管理中心实现兄弟组件通信。
生命周期的变更:
新增服务端渲染,就是可以在服务端渲染好组件,将渲染结果直接传给前端使用,通过renderToString或者renderToStream方法实现,加快页面响应速度。
Virtual DOM
该特性也是2.0新增的,对于以前web项目,我们如果想要实现比较好看的效果或者功能,经常会写很多操作DOM元素的代码,而真正的DOM元素包含了非常多的东西,非常复杂庞大的,所以如果我们经常操作DOM的添加删除就会影响页面渲染速度,从而提出一种优化方案,让JS的对象代替DOM元素对象,实现一种树形结构,这就是Virtual DOM技术,也就虚拟DOM;当然最终肯定是要操作真实DOM对象的,但是我们可以不用中间操作,直接对照结果操作DOM树,例如,添加删除添加再删除同一个元素,最终结果肯定是删除该元素,但是中间有四步就导致慢了。
底层原理就是下图所示:
vnode 有两种生成方式,一种是普通DOM元素生成,一种是Vue的组件生成,区别就是普通生成的 vnodeComponentOptions 值是空的,这个是用来描述vue组件特有的一些参数的,下图是生成 vnode 的过程。
从上面我们可以看到 render 方法生成有两种,原本就有 render 字段的,以及没有该字段用的模板或者根元素的;ast 语法树优化过程做了两件事,检测静态 class 名和 attributes ,以及检测最大静态子树,这样在初始化渲染过后就不会再对比它们,直接跳过对比然后复用。
创建 vnode 对象在源码里是 renderElementWithChildren 和 renderElement 方法;将 vnode 转为真实的 DOM 是通过源码里的 patch 方法;
服务端渲染技术
优点:
- 首次进入页面渲染速度更快,由于浏览器第一次加载页面还没有缓存,所以需要到服务端下载js和相应资源渲染页面,而使用该技术前端可以直接解析html文档并渲染页面。
- 该技术可以让搜索引擎更容易读到页面的meta信息和其他SEO相关信息,大大增加了网站在搜索引擎里的可见度。
- 服务端渲染可以在首次渲染时,将动态数据同步输出到页面,而使用普通的方法则需要请求一次服务器数据,减少了http请求次数。
流式服务端渲染技术是普通服务端渲染技术的优化,普通的版本有一个缺点就是渲染是同步的,如果网页比较复杂可能会导致服务器的event loop阻塞,而该技术则使用通道和流的方式优化了。
源码篇
vue内部封装了一些常用的方法,以便用户使用,而不额外引入其他的框架,util一共有6个部分:env,dom,components,lang,debug,options。
transition 和 animation 的区别就是前者需要一个用户事件去触发,而后者是显示的随着时间变化改变。
书本里介绍了很多框架核心功能的主要实现代码,书中没有给出全部的实现代码,所以解决不了所有的疑惑,所以需要自己配合源码查看才有效,光是看书理解不够深而且记忆不深刻。
个人理解:我也学习了很多框架,看了很多框架的源码,但是现在这个互联网环境,框架层出不穷,技术不断更新,人不能把所有的框架源码都看一遍,源码不要过于详细查看,真的是想要把每个变量都看懂,不要这样,相信我,你绝对看了不超过一个星期就会将这些忘掉,我们更应该着重看的,学习的是编写代码的方式,代码结构模式,核心算法实现,功能实现的原理如何等,我们要学会将自己带入开发者,看了框架实现的原理后,想想自己应该怎么实现同样的一个功能,这样有助于自己看源码的一个效率,人的时间有限,不要过度拘泥于细节,看不懂没关系,读书百遍其意自见;其实这也适用于现在的开发,为什么说是现在呢,因为现在的AI解决了大部分开发的简单任务,比如我想要实现一个什么样的功能或者什么样的一个方案,直接输入,唰的一下就给了你答案,而且还有不同的实现方案,相较于以前获取信息更快速,渠道更统一,所以现在还在背什么工具或函数的使用方式,框架的一些配置啥的,都没必要,知道有这么个东西就行,但是前提是必须要理解其中原理,AI毕竟是通过以往的经验学习的,所以有些时候遇到某些问题AI无法解决,这就需要懂原理的人才能找到问题解决了。当然如果是为了面试,那就当我没说。
响应式原理图:
数据times其实就是model,而前端显示的页面就是view,可以看出是通过观察者模式实现的。
Vue 里的缓存是参考了 js-lru ,是基于LRU(最近最少使用)算法实现调度的。
Vue对象里event的初始化流程:
Vue里event的相关函数 $emit , $on , $off , $dispatch (调用,添加,删除,分发)等,其实本质就是操作 this._events[] 事件数组。分发会遍历 $parent 调用事件,而广播则是遍历 $children 。
props的初始化流程:
webpack
webpack 是一个模块化加载器,同时支持amd,cmd等加载规范,有以下优势:
- 代码分割,支持同步和异步加载依赖,同步会将依赖编译时直接打包输出到文件,异步则是生成一个代码块,在浏览器运行加载需要的时候才异步加载该代码块。
- 我们可以把其他类型资源转为JS文件加载。
- 拥有强大的插件系统。
安装以及使用
webpack 打包后的文件可以运行在任何前端的环境,打包的命令可以有很多参数,在使用的时候查询即可。
npm i webpack -g // 安装命令
webpack app.js app.bundle.js // 简单的打包命令,app.js 是入口文件,而后面参数是打包输出文件
对于复杂的工程我们可以使用 webpack.config.js 配置文件去设置打包配置,方便使用和管理,webpack会默认加载根目录下的这个文件,如果改了名字则使用 -config 参数去指定,配置文件内容需要使用 module.exports 导出。
context :设置运行webpack命令的路径;entry :指定入口文件,路径相对于 context ;output :输出路径和文件名配置;module :加载模块的配置;resolve :依赖文件的配置;devserver :webpack-dev-server的配置,用来运行调试的。
webpack-dev-server
这是一个基于 express 的 node.js 服务器,文件改变时,会自动触发打包机制,然后通过 Sokect.io 通知浏览器页面刷新。
使用 node 安装,一样的命令写法,运行命令直接就是名字 webpack-dev-server ,可以接收参数,也可以在配置文件里写参数。
ProvidePlugin
该插件可以自动加载当前模块依赖的其他模块,并且使用别名的方式注入到当前模块中,例如:当前模块依赖 Jquery ,我们可以在配置中写好 Jquery 模块的别名为 $ ,之后我们在其他模块中使用时,直接就用 $ 该符号就可以了。
Rollup
普通打包的方式会将所有依赖的第三方组件都打包输出,但是如果我们只是需要第三方组件的某些函数,其他的一概不要,这就导致了打包后的文件偏大,而且也影响前端页面的加载速度,但是现在有 ES 6 的模块打包工具,使用 Tree-shaking 技术,可以将需要的代码提取出来打包,而且会将模块内容置于同一个作用域下,能直接在代码里使用变量名,而无需引用模块定义变量。
Browserify
也是一种模块化打包工具,和 webpack 类似。
Vue-Loader
该插件是基于 webpack 写的插件。
PostCSS
该插件用来将 css 的操作转为 JS 的方式去使用,能够方便使用而且实现各种需要的功能。
拓展篇
Composition Event
复合事件是 DOM 3级事件中新添加的一类事件类型,用来处理 IME 的输入序列,也就是输入法工具,让用户可以输入在键盘上找不到的字符。
ES 6
es 6 (ECMAScript)是 js 的一种标准,相当于就是 js 的一种架构设计,就像造车的蓝图一样,而且 ES 6 引入了很多新的特性,也解决了之前的很多 js 的问题。
模块
在以前 js 是没有模块化的这个概念的,虽然易于学习和开发,但是难以维护,所以当 es 6 出来后也引入了模块化概念;在这之前都是开发者社区里制定的一套方案,比较出名的就是 commonJS(node模块化加载)和 AMD(RequireJS)规范,前置用来服务器环境,后者专注浏览器环境。
当a模块引入b模块的一个原始值,如果a模块修改了该值,b模块也会相应的同步修改。
let
这是声明变量的一种修饰符 let 和 var 一样,不同的是 let 声明的变量只能在代码块作用域内使用,而 var 则是都可以使用;let 不存在变量提升,必须声明后才能使用。
Object
object对象里有很多可使用的函数,用来操作对象,或者继承对象;
函数柯里化
意思就是在一个函数里,可能存在某些传入参数是固定或已知的,我们可以通过将传参分为多步的方式,在第一次传入参数后,后面的使用就可以重复利用该参数,不需要再次传入,相当于第一步的传参直接完成了。
// 下面就是使用了该方式,如果按平时我们的普通写法,直接在sum方法里返回x+y了
function sum(x){
return function(y){
return x + y
}
}
// 但是函数创建的时候相差不大,但是使用却有所不同
var test = sum(10);
test(20); // test(30) 也是可以的,相当于10 + 任何数,我们将传参分了两步,第一步是初始化时,第二步是使用时
// 我们在后续使用的时候就可以不用再传10这个数了,其实就是用来我们在第一次知道是什么数据后,后续一直使用该数据的情况
// 如果第一步里有条件判断的情况,那么在初始化后返回函数,后续直接就可以不用再次判断了,跳过第一步了