木灵鱼儿 - vue前端开发快速入门与专业应用
实习的地方使用的vue，于是只能硬着头皮学了，之前买过一本vue的书，于是派上了用场，但是实际其实用处感觉不大，前期的内容还是挺简单的，就是讲了几个vue的标签，指令这些的用法，但是一到路由这里就卡主了，因为1.0和2.0版本不同，很多东西都变了，而且例子也不详细，无奈只能放弃继续读下去，于是找一个视频教程和一本项目实战的书，期待能有新的认知，这里就暂时放弃继续写这本书的笔记了。
基本写法首先引入vue的插件router，地址：https://unpkg.com/vue-router/dist/vue-router.js下载后head引入,然后创建好html的标签: 主页 列表 关于 使用router-link标签to属性表示路径,使用router-view用于显示组件入口.var home = Vue.component('my-home',{ template : ' 主页 ' }); var list = Vue.component('my-list',{ template : ' 列表 ' }); var about = Vue.component('my-about',{ template : ' 关于 ' });我们再创建三个组件用于显示,可以使用Vue.extend或者Vue.component,都要用一个变量引用.var routes = [ {path:'/',component:home}, {path:'/list',component:list}, {path:'/about',component:about}, {path:'*',redirect:'/'} ];创建一个数组变量,里面使用对象来编写路由路径,每个{}表示一个路由页面,path则是浏览器地址栏显示的地址,component的参数为之前创建的组件的变量名.其中{path:'*',redirect:'/'}表示重定向,一般写在最末尾,表示当地址栏指向的地址非指定好的path时,会自动跳转到home,类似于404提示.var router = new VueRouter({ routes : routes });路由配置文件写好后自然要创建路由了,new VueRouter创建路由实例,routes的值要为刚刚创建的配置文件routes变量,我们也可以写简写形式,如:var router = new VueRouter({ routes });直接传入这个配置的变量名即可.至此路由创建完毕,我们还需要在对应的vue中引用才可以正常使用.var app = new Vue({ el : '#app', router });在对应的Vue实例中加上router即可.二级路由一般来说不可能就只有一个一级路由,二级路由甚至三级路由,等等,我们现在就可以看看二级路由.假设我要在一级路由显示的biz组件里面再加两个路由地址,然后这两个二级路由也有对应的router-view;首先创建一个Biz组件var Biz = Vue.component('my-biz',{ template : ' \ 二级路由 \ \ 二级路由1 \ 二级路由2 \ \ \ ' });html上加上这个路由链接元素 主页 列表 关于 Biz 路由器配置var routes = [ {path:'/',component:home}, {path:'/list',component:list}, {path:'/about',component:about}, {path:'/biz',component:Biz, children:[ {path:'list',component : { template : ' 二级路由进来了1 ' }}, {path:'about',component:{ template : ' 二级路由进来了2 ' }} ] }, {path:'*',redirect:'/'} ];在biz的对象中添加一个children数组,表示子路由,里面也是一样有path,component,注意子路由path不需要反斜杠表示.component里面template和平时写的一样,你可以直接写html文本,也可以用#xx 获取htm文件中的对应的组件.基本二级路由就完成了,如果三级路由就继续这样嵌套就行了,但是vue不建议嵌套太多,本身就是作为单页面app开发用的注意:组件的html文本必须要有一个根元素将所有的内容包裹路由匹配如果我们要复用一个组件,只是路径名发生了变化,就可以使用路由匹配,比如一个user的组件,不同id的用户都要用这个组件来渲染,我们可以通过匹配他后面带的用户名参数来判断.var Biz = Vue.component('my-biz',{ template : ' \ 二级路由 \ \ 二级路由1 \ 二级路由2 \ \ \ ', watch : { '$route'(to,from) { console.log(to) console.log(from) } } }); 我们对Biz组件加多一级路径,一个/foo,一个/bar,然后使用watch监听这个路由的变化,to是最新路由地址,from则是过去的路由地址,这两个都是一个对象,里面的params可以拿到路由匹配的东西,前提是路由配置哪里已经设置好了.否则只能通过path属性来查看完整路由地址.var routes = [ {path:'/',component:home}, {path:'/list',component:list}, {path:'/about',component:about}, {path:'/biz',component:Biz, children:[ {path:'list/:name',component : { template : ' 二级路由进来了1{{$route.params.name}} ' }}, {path:'about/:name',component:{ template : ' 二级路由进来了2{{$route.params.name}} ' }} ] }, {path:'*',redirect:'/'} ];我们再biz的二级路由上加上了一个/:name,这样表示自动匹配/后面的内容,并且在params中name:xxx的键值对的形式存在,这个name可以自己自定义名字.在template中插入{{$route.params.name}}来获取到对应的name.事实上我们还可以进行多个匹配,无非就是/:name/:id/:user这种形式写法,中间也可以使用普通路径/:name/path/:id这样,都是可以的,只要进入了这个对应的路由,params都可以获取到对应的路由地址的值.*全匹配符号这个符号可以理解为正则中的*任意字符匹配,他连/也可以匹配到,比如我有一个路由地址为/user-aaa/sadad/asdasda,使用全匹配可以这样写/user-*,然后就可以自动匹配到后面所有的值.当然你可以限制一下,比如到sa结束/user-*sa
component通过compoent标签加上:is就可以判断让哪些组件显示。compoent可以理解为一个所有组件的母体，它包含了所有的组件，通过:is的值就可以让对应的组件显示出来。 主页 列表 关于 var app = new Vue({ el : '#app', data : { showview : 'home' }, components : { home : { template : ' 主页 ' }, list : { template : ' 列表 ' }, about : { template : ' 关于 ' } } });keep-alive:is属性每次让元素显示或者隐藏都是一次完整的渲染，如果说这个显示的内容是不需要每次显示都更新一次，那么就可以使用keep-alive标签包裹，这样vue就会将隐藏的内容暂存在内存中，显示时在拿出来，以减少性能消耗。 主页 列表 关于 var app = new Vue({ el : '#app', data : { showview : 'home' }, components : { home : { template : ' \ 主页 \ \ {{item}} \ \ ', data(){ return { items : [] } }, mounted : function(){ console.log('1'); this.items = [1,2,3,4,5] } }, list : { template : ' 列表 ' }, about : { template : ' 关于 ' } } });我们给home组件添加一个mounted的生命周期钩子，表示元素渲染后会运行里面的内容。当我们切换的时候，会发现，除了第一次console.log('1')运行了，后面都没有再重复了，表示这个生命周期并没有结束， keep-alive有效。注意keep-alive也可以不和component配合使用，keep-alive可以包裹多个子组件，只要确保所有的子组件只要一个是激活状态，其他都是隐藏的，比如使用v-if/v-else。集中式事件处理器由于子组件和父组件之前是相对独立的，那么事件的创建触发相对来说会很麻烦，像之前的使用props来接受父组件的方法，$emit来传参这些都很麻烦，我们可以创建一个空的vue实例，然后对这个实例进行事件的集中处理。首先创建一个空的vue实例var bus = new Vue();然后对这个实例使用$on方法创建并监控一个自定义事件和方法bus.$on('create', function(data) { alert(data); })然后我们通过$emit来触发这个bus的自定义事件，就可以做到快速传值了，也没有之前做的那么麻烦假设我们要在某个按钮click后才触发bus.$emit('create','666');通过一个全局的实例bus，我们可以在任何地方访问，方便于集中管理，所有的通信类事件都可以通过该方式做到，并且还可以在函数作用域下拿到父级的this来操作对应的内容。然后触发$emit和监控$on，都可以自由的放置在不同的组件中，注意on要先运行。
class类名之间的区别单个说明不是很好理解，我们直接看它的流程图v-enter(动效开始之前)>------>------>------>v-enter-to(动效结束) v-leave(动效开始之前)>------>------>------>v-leave-to(动效结束)这两个表示一个来回，第一次点击运行第一个v-enter,第二次点击运行v-leave，这样有来有回就产生动效。两个动效一般都不会在类名里面写transition：all .3s；这种来控制动效时间，他们都有一个通用的类名，在开始到结束一直存在的：v-enter-active 第一次的v-leave-active 第二次的这两个主要存放动效的transition属性假设我们要做一个opacity的过渡，我们要这样写.v-enter { opacity:0; } .v-enter-to { opacity:1; } .v-enter-active { transition: opacity 1s ease; }这样写表示第一个流程，第二个流程实际上就是反过来，css就没必要重新写了，直接共用.v-enter,.v-leave-to { opacity:0; } .v-enter-to,.v-leave { opacity:1; } .v-enter-active,.v-leave-active { transition: opacity 1s ease; }那么这里一个动效类名就写好了，实际上这个类名就是vue默认调用的一个类名，也就是如果你没有指定transition元素name属性，他就会默认使用这个类名作为切换的效果。注意：过渡的是active记得要写在最后，让他的css权限比enter，leave大，以免过渡时这两个里面写了transition属性造成动效出现问题！实际上这个css还可以继续简写，我们元素默认就是显示的，不管opacity为1，他也是显示的，除非你设置为0，那么vue操作类名的时候不会像jq一样，操作完结束时是什么，元素上就是什么，vue会将class类名全部清除掉，就好像动效之后什么都没有发生，那么我们甚至可以不写结束时的类名。首先我们看下流程：第一次点击，元素先添加了.v-enter，瞬间将元素设置为动效开始前的状态，此时元素透明为0，然后移除.v-enter，接着添加.v-enter-active和v-enter-to通过active产生动效，当他达到1时，class被清除。第二次点击，也是一样。那么由于元素本身就是现实的，移除opacity，不设置动效结束时opacity为1，在active的影响下元素也会发生透明度动效。以上就是class类名的理解，然后就是自定义类名了，因为我们不可能都是使用默认的吧！自定义类名transition有一个name属性，它的值可以为自定义的class类名，比如name=“fade”然后我们创建一个fade的cass效果.fade-enter,.fade-leave-to { opacity:0; } .fade-enter-to,.fade-leave { opacity:1; } .fade-enter-active,.fade-leave-active { transition: opacity 1s ease; }实际上我们只需要把之前的class类名v改为我们自己的name值，然后就可以了，vue会自动补全后面的值，因为后面的都是固定的，所以在设置的时候，只需要name=“fade”即可。动态绑定动效名只要为transition的name绑定一下就行了 绑定后他会去获取data里面的transitonName的值，当我们修改了它的transitonName的值时，transition的name值也会发生变化，这样就可以做到随心变化了，我们只需要设置好css效果然后添加就行了，每次只用改data里面的值，这样就不用在繁多的transition中一个个修改。引入animate动画库css动画库有一个比较有名的，就是animate.css了，vue可以通过引入这个css文件然后使用它提供的动画效果。animate官网下载后在head里link引入这个css文件，然后在vue的transition元素上添加一下是哪个属性：enter-active-class 表示第一次动画效果leave-active-class 表示第二次动画效果:duration 表示动画的时间 enter-active-class和leave-active-class如果引入的是animate.css前面一定要加上animated专门的class类名。animated这个类名实际上就是设置了动画的时间以及循环次数，一般来说官方在每个动画class上都预设好了对应的时间，懒人就可以直接用，所以我测试不写这个的话，也没什么问题。而:duration也可以不用，因为css里面都设置好了时间，如果你想调整时间又不想去动css，那就直接使用这个属性，该属性的值有两种方式：:duration="{enter: 500, leave: 800 }" //这种表示enter的用时，leave的用时，单位s:duration="500" //这种表示enter和leave都是500s以上就是引入第三方动画库的使用教程。详解transition标签transition除了支持以上那些属性外，还有几个属性值得一看：appearmodeappear是一个单属性，表示transition里的子元素第一次渲染是是否使用动画效果，默认是不显示动画效果的。 mode表示动画的过渡顺序，如果transition里面有v-if/v-else，他们的动画触发是同时存在的，也就是同时发生，这就会造成一些问题，比如我想让这个按钮先消失，另一个才出来，于是便要用到mode了有两个个值：out-in 表示隐藏/消失的元素动画先运行，在运行显示的动画in-out 表示先运行显示的元素动画，隐藏/消失的在之后运行 函数钩子transition也可以使用纯js来操作动效，这里就要用到函数钩子，每个钩子就好像class的类名一样，什么状态做什么操作，所有的操作方法全部写在vue对象实例的methods对象里面。首先说一下有哪些钩子beforeEnter 动画开始之前的初始状态enter 动画进行中的状态afterEnter 动画完成后状态enterCancelled 进入过渡被打断时beforeLeave 第二次动画开始前的初始状态leave 第二次动画进行中的状态afterLeave 第二次动画完成后的状态leaveCancelled 动画过渡被打断的状态为了更方便理解我想到一个理解方式，你可以把enter作为显示时调用的动画，leave作为隐藏是调用的动画，之前的那些动画运行的类名也可以这么认为。html写法： jsvar app = new Vue({ el: '#app', data: { show: true }, methods: { beforeEnter(el) { console.log('show动画开始前', el) }, enter(el) { console.log('show动画进行中', el) }, afterEnter(el) { console.log('show动画结束后', el) }, enterCancelled(el) { console.log('show动画被打断', el) }, beforeLeave(el) { console.log('hide动画开始前', el) }, leave(el) { console.log('hide动画进行中', el) }, afterLeave(el) { console.log('hide动画结束后', el) }, leaveCancelled(el) { console.log('hide动画被打断', el) } } });每个绑定的对应的函数都 有一个参数，这个参数就是动画元素本身的dom元素。在实际测试中被打断的状态一直没测试出来，所以这两个用不到，以后碰到了再单独讲讲，在元素显示和隐藏是这几个钩子函数会依次运行，开始---进行---结束，通过el参数进行原生的js改动样式。(稍微看了下，打断的触发，在v-if时是不会触发的，而在v-show时便会触发，主要区别就是当元素还在进行过渡是改变的绑定的show的值，true改为false，v-if会继续执行未完成的动画，比如先true，瞬间又改为false，那么元素会从show动画执行完再执行hide动画，再移除元素，而v-show则是show动画停止了，被打断，然后马上执行hide动画，再display:none!实际测试硬是没感觉出来，也没触发到，用处不大)但是实际上，即便我们写了钩子函数，没有填写name属性，vue也会默认添加默认的class类名v-enter这些，所以如果你想你的动效全部都交给js来做，记得添加一个name=“xxxxx”的无意义命名，只要这个类名css中没有写过样式就行，这样就是可以全权交由js负责了。js动画库我找到一个开源的js动画库，但是目前还没搞清楚怎么使用，先分享出来anijs.jsvue2.0新增加的动画函数钩子我们之前讲过appear可以让元素在第一次渲染时，也就是第一次show的时候触发动画效果，那么2.0也更新的对应的钩子。before-appera 初次显示前的初始状态appera 初次显示中after-appear 初次显示结束一共就三个，使用方法和之前的钩子函数一样，通过v-bind绑定methods中对应的函数，然后el的原生dom参数，js控制。transition-group一般来说，一个transition包裹一个元素，然后给该元素添加show、hide动效，但是我们可以通过v-if/v-else判断，可以让两个元素都可以使用该动画，但是这基本上就是“二龙不想见”，如果我们要有很多个元素怎么都用同一个动画，你不可能每个都用一个transition标签包裹吧！所以这里就有了transition-group标签了，这个标签可以给他里面的元素添加统一的过渡样式。样式设置和transition一样，只是没有了mode这个属性。要求：子元素都要有一个唯一的key值，我们知道v-for的时候可以用index数组下标作为它唯一的key值，如果不是v-for你其实可以瞎鸡儿编的，只要key值唯一就行，你纯字符也行啊。transition-group支持一个tag的属性，tag="ul"表示所有的子元素将被ul元素包裹，实际上就是把transition-group标签替换成ul了，通这个属性可以自由控制包裹的父元素。 {{item.text}} var app = new Vue({' el : '#app', data : { items : [ {id:1,text:"第一个"}， {id:2,text:"第二个"} {id:3,text:"第三个"} {id:4,text:"第四个"} ] } });基本vue的过渡就到这里了，以后有机会再来实践！
由于2.0版本取消了所有的内置过滤器，而且过滤器只能在插值中使用，也就是{{xxx}}中使用，格式为{{name | 过滤器}}；通过一个管道符号将前面需要过滤的内容隔开，后面写一个函数用于过滤，过滤器会将name作为第一个参数传入这个函数中，并且还支持传参。创建一个过滤器全局过滤器Vue.filter('deta',function(value){ if(!data instanceof Data) return value; return value.toLocaleDateString(); });通过Vue.filter创建一个全局过滤器，deta为过滤器名字，后面接一个函数，第一个则是管道符号前面的值，该方法的意思是，如果这个value不是Date对象下的实例，那么就直接返回这个对象，反之则将这个value转换成本地时间格式返回，一般为2019/11/9这种，然后使用的时候就是这样： {{date|deta}} var app = new Vue({ el : '#app', data : { date : new Date() } });该方法还支持传入多个参数，但是其实用的还是参数接收，格式如下：Vue.filter('deta',function(value,parameter){ //parameter就是传入的参数 }); {{date|deta(parameter)}} data()在括号里传入参数即可，你可以传数组，也可以传string，obj，甚至可以传动态参数，比如我们在vue的data里面添加一个flag:true的参数用于判断这个东西是否需要过滤，当然这个也只是一个例子 {{date|deta(flag)}} 需要注意的是如果是动态参数就不要加引号，如果不是则要加上引号，具体自行测试。局部过滤var app = new Vue({ el : '#app', data : { date : new Date() }， filters : { name : funciton(value){ //局部过滤器 } } });直接再vue对象里面加入filters参数，其他都一样，vue有一个规律，凡是全局有的，在局部里使用都加了一个s。双向过滤器除了动态参数外，其他的都是在元素渲染至dom前就进行过滤，并不影响到数据本身，也就是data里面的值不会发生变化，使用双向过滤就可以将data绑定到的属性进行写入。Vue.filter('deta',{ read : function(value) { //渲染到dom时 if(!data instanceof Data) return value; return value.toLocaleDateString(); }, write : function(){ //写入到data时 if(data instanceof Data) { return value.toLocaleDateString(); } } });实际上read和write没必要都要一样，比如人民币的换算，一般为了避免浮点数的运算，都是在服务器里存储整数的，一般以分为单位，已知1元=10角=1000分，以分为单位。用户显示和输入都是以元为单位。Vue.filter('money',{ read : function(value) { //渲染到dom时 return '￥:'+(value/1000); }, write : function(){ //写入到data时 return value*1000; } }); var app = new Vue({ el : '#app', data : { money : 5000 } }); {{money|money}} 2.0移除了这个东西，我日连续过滤 {{money | money | money}} 通过管道符号隔开，之前的过滤器return的值会作为后面过滤器的参数再次过滤，以此类推。
v-if/v-else看书上说v-show也可以和v-else配合使用，实际上并不行。:style 也支持三元判断，和:class一样，三元需要加个括号，然后里面如果是普通的string值需要用引号括起来，不然会认为是对象或者Vue对象的属性方法那些，从而报错。例子： v-for需要为每个循环出来的对象加上:key=“index”，index就是对应的下标v-text{{msg}} == v-text，但是v-text可以避免在内容还未更新时先出现{{msg}}这个字符，然后再被替换成对应文本的闪现。v-html原来可以通过{{{data}}}的方式来插值，现在不行了，只能通过指令v-el2.0放弃了这个方法，使用rel=“xxx”来获取对应的dom元素，然后通过$refs.xxx来获取对应的dom对象，如果在vue对象里面就使用this.$refs.xxx如果再外面，就使用实例的名字.$refs.xxx来获取注意，title-h2表示驼峰命名，调用时等于titleH2ref也可以给子组件添加标记，效果一样v-pre阻止{{}}插值的内容被替换渲染，直接输出插值的文本，然后使用了v-pre，通过ref获取的标记也会消失，使用$refs.xxx便会报错 {{msg}} v-cloak延时，如果渲染的内容过多，可以使用单属性v-cloak来隐藏未编译的{{}}mustach插值，等对应的实例对象编译完成再显示。 {{msg}} v-once表示该元素及子元素或者包含的组件只会渲染一次，哪怕我们后面修改了绑定的数据，他也不会再页面变化。 {{msg}} 自定义指令指令注册注册全局指令Vue.directive('name',{});局部指令在子组件extend或者component中加入directives，该局部指令只能在该子组件上使用Vue.extend('templeta',{ directives : { 'local-class' : {} //-连接表示驼峰命名，调用是v-localClass } });定义指令的对象有三个属性：bind：只会调用一次，就是当指令绑定到元素上时update：元素内容第一次渲染以及后续更新内容绑定内容时运行，它接收两个参数，为newValue和oldValueunbind：指令从元素上解绑时触发Vue.directive('wCc',{ bind : function(){ console.log('bind',arguments) }, update : function(newValue,oldValue){ console.log('update',newValue,oldValue) }, unbind : function(){ console.log('unbind',arguments) } }); var app = new Vue({ el : '#app', data : { value : '初始化', flag : true, html : ' 我是一个HTML ' }, methods : { } }); {{html}} 首次运行时我们可以看到bind的arguments包含了几个参数，其中0为绑定的元素本身，1则是指令绑定的内容，剩下几个就不是很清除了，以后再研究修改内容在浏览器输入：app.value = ‘内容更新了’我们可以看到update运行了，他的newValue实际上是被绑定的div元素本身，而oldValue是一个object对象，他里面包含了上一个内容和新的内容，展开后他的oldValue属性是旧的绑定内容，value则是新的内容。取消绑定时取消绑定实际上就是让元素消失，v-if这些，我们给他加个flag判断，然后将flag改为false。实际上和bind一样，所以就不多解释了。如果我们只需要使用update的话，可以使用简写形式创建Vue.directive('wCc',function(value,value2){ console.log(value,value2);}); 直接传一个带参数的函数就行了，va1，va2对应上面说的。 如果你的指令绑定是一个string值，那么update只会运行一次，因为你的值不是vue对象的，那么就不能进行响应，而且传的如果是string需要用引号括起来表示，如：`v-w-cc="'string'"` vue2.0后自定义指令又增加了几个属性： 1. `inserted` 被绑定元素插入父节点时调用 (仅保证父节点存在，但不一定已被插入文档中) 2. `componentUpdated`指令所在组件的 VNode 及其子 VNode 全部更新后调用。 之前说的参数都比较浅显，我们来深入一下 这些属性都对应着一个function，这个方法里面有几个对应的参数名用于理解 inserted : function(el,binding,vnode,oldVnode){ //el就是指令所绑定的元素，可以用来直接操作 DOM //binding一个对象，包含以下属性: //name：指令名，不包括 v- 前缀。 //value：指令的绑定值，例如：v-my-directive="1 + 1" 中，绑定值为 2 //oldValue：指令绑定的前一个值，仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。 //expression字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中，表达式为 "1 + 1" //arg传给指令的参数，可选。例如 v-my-directive:foo 中，参数为 "foo" //modifiers一个包含修饰符的对象。例如：v-my-directive.foo.bar 中，修饰符对象为 { foo: true, bar: true }//vnode Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情（实际上就是一个容器，用于包含这个绑定的元素） //oldVnode 上一个虚拟节点，仅在 update 和 componentUpdated钩子中可用} **除了 `el` 之外，其它参数都应该是只读的，切勿进行修改。如果需要在钩子之间共享数据，建议通过元素的 [dataset](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/dataset) 来进行。** 然后之前说过只传入一个带参数的function不写{}对象来保存对应的参数，他其实表示再bind和update阶段都会运行，如果你只需要这两个阶段运行的话，就可以直一个带参数的funciton 原有的update不会再初次渲染是运行，而是值发生变化时运行，而且只要组件发生重绘一次就会运行一次，如果需要过滤掉不必要的更新可以在里面判断value和oldValue是否==这些。 biding参数全部都是只读的，修改了也不会发生变化，如果要修改值，可以直接el dom操作。