用vue进行开发到目前为止也有将近一年的时间了,在项目技术选型的时候隔壁组选 react的时候我们坚持使用vue作为前端的开发框架。虽然两者思想上的差异不大,但是vue的语法在代码的可读性以及后期的维护成本更加的适合,而且最近看到Apache对react的相关许可限制;这里不讨论react和vue的对比哪个好,技术框架没有最好的,只有适合项目才是最好的。
进入主题。。。。。。。
组件,是vue的核心之一。
我们可以把页面各个子模块看成一个组件,可以独立拆分出来。这样不仅维护变得简单了,而且代码复用性也高。
vue组件分为全局组件和局部组件。组件中属性和vue实例基本类似,基本可以使用其所有属性如computed,methods,components,filter,directive.....但data属性不同,在组件中data是函数,而且数据需要return出来;因为组件可能会被引用多次,就会创建多次实例,如果data是个对象的话那引用到这个组件的地方都将公用一个data这样就回造成了数据的污染。如果使用data属性函数返回一个对象的话就可以解决这个问题,每次引入到这个组件实例的时候就可以在data函数中返回一个初始的数据对象。
组件的介绍
全局组件
使用Vue.component进行全局注册,所有vue实例都会共享此组件
1 <div id="app"> 2 {{msg}} 3 <const-comp></const-comp> 4 5 </div> 6 <script> 7 8 Vue.component('constComp', { 9 template: "<h3>我是全局组件</h3>" 10 11 }); 12 13 new Vue({ 14 el: "#app", 15 data: { 16 msg: "hello component" 17 } 18 }); 19 20 </script>
局部组件
局部组件只能在引入当前的vue实例中有效,在当前vue实例中components属性加上引入进来的组件实例即可
<div id="app"> {{msg}} <!-- <const-comp></const-comp> --> <local-comp></local-comp> </div> <script> /*Vue.component('constComp', { template: "<h3>我是全局组件</h3>" });*/ let localComp = { template: "<div>我是局部组件</div>" }; new Vue({ el: "#app", data: { msg: "hello component" }, components: { localComp } }); </script>
单文件组件(xxx.vue)
其实就是将写在js中的组件提出到一个vue文件中写而已,这样组件更加的好维护以及阅读性也会好,提取出来了相应的引入即可,不会显得文件很多行很长。
其主要有<template></template><script></script><style></style>这三个标签,每个标签做自己的事。template就像我们在html中写dom,script写js代码当前的组件实例,style写组件样式,注意:加上scoped即可使当前样式只在当前组件生效,组件渲染的时候此组件的dom会加上data-v-xxx属性来选择当前组件样式。如果没加上scoped的话当前组件的样式就会在引入这个组件的实例中造成影响
如我写的一个found.vue文件demo
1 <template> 2 <div> 3 <div class="saerchDiv"> 4 <Search 5 @result-click="resultClick" 6 @on-change="changeResult" 7 :results="results" 8 v-model="searchVal" 9 position="absolute" 10 auto-scroll-to-top 11 top="46px" 12 @on-focus="onFocus" 13 @on-cancel="onCancel" 14 @on-submit="onSubmit" 15 ref="search"></Search> 16 </div> 17 18 </div> 19 </template> 20 <script> 21 import {Search} from 'vux'; 22 23 export default { 24 name:"found", 25 data() { 26 return { 27 msg:"found page", 28 // searchVal:"寻找更多好文章", 29 searchVal:"", 30 results:[] 31 } 32 }, 33 components:{ 34 Search 35 }, 36 methods:{ 37 resultClick(item) { //选中搜索 38 console.log(item.title); 39 }, 40 changeResult(val) { //获取搜索关键字 41 console.log(val); 42 /*this.$http.get('').then(res => { 43 44 }).catch(err => { 45 46 });*/ 47 48 this.results = this.getResults(val); 49 }, 50 onFocus(){ 51 console.log("on focus"); 52 // this.searchVal = "" 53 }, 54 onCancel(){ 55 console.log("点击取消按钮"); 56 }, 57 onSubmit(){ 58 console.log("on submit"); 59 }, 60 getResults(keyword) { //暂时获取假数据 61 let rsArr = []; 62 for(let i = 0; i < 6; i++) { 63 rsArr.push({ 64 title:keyword + (i+1), 65 other:i //文章id 66 }); 67 } 68 return rsArr; 69 } 70 71 } 72 } 73 </script> 74 <style scoped> 75 76 .saerchDiv { 77 height: .75rem; 78 font-size: .27rem; 79 } 80 81 </style>
组件的通信
vue组件的通信是vue组件的核心,组件不仅仅是要把模板的内容进行复用;更主要的是组件间要进行通信;组件之间的通信数据传递是组件的生命力之一。
props单向数据流,父组件向子组件传递数据
props可以是一个数据类型也可以是一个数组,也可以是对象,对象下的数据有3个属性type,default,require。其中default,require值都是布尔类型值。type有Number,String,Boolean,Array,Object,Function,Symbol。如果props数据是对象或数组时默认值default必须是一个函数来返回初始化数据。而且因为对象或数据是引用类型,指向的是同一个内存空间所以当props数据是这两个类型时,数据改变时子组件内改变是会影响父组件的。
props数据类型及相关样例
1 // props: ['propsDataA'], 2 props: { 3 propA: { 4 type: String, 5 default: "", 6 require: true 7 }, 8 propB: { 9 type: Number, 10 default: 1, 11 require: false 12 }, 13 propC: { 14 type: Array, 15 default: function() { 16 return []; 17 }, 18 require: true 19 }, 20 propD: { 21 type: Object, 22 default: function() { 23 return {}; 24 }, 25 require: true 26 }, 27 propE: { 28 type: Function, 29 fn: function(val) { //一个时间未满两位数前面补零验证 30 return val > 9 ? val : '0' + val; 31 }, 32 require: true 33 }, 34 propF: { 35 type: Boolean, 36 default: false, 37 require: true 38 }, 39 propG: [String, Number], 40 propH: Number 41 }
这里稍微改动一下局部组件的代码,父组件向子组件传递数据;
1 <div id="app"> 2 {{msg}} 3 <!-- <const-comp></const-comp> --> 4 <local-comp :props-a="info"></local-comp> 5 </div> 6 <script> 7 8 /*Vue.component('constComp', { 9 template: "<h3>我是全局组件</h3>" 10 11 });*/ 12 let localComp = { 13 template: "<div>我是局部组件<p>父组件传过来的数据为-->{{propsA}}</p></div>", 14 props: { 15 propsA: { 16 type: String, 17 default: "", 18 require: true 19 } 20 } 21 }; 22 new Vue({ 23 el: "#app", 24 data: { 25 msg: "hello component", 26 info: "hello props" 27 }, 28 components: { 29 localComp 30 } 31 }); 32 33 </script>
自定义事件$emit,子组件向父组件通信
这里还是在原来的基础上改,子组件使用$emit自定义一个send事件向父组件发送数据
1 <div id="app"> 2 {{msg}} 3 <!-- <const-comp></const-comp> --> 4 <div>子组件数据为---->{{fromChildData}}</div> 5 <local-comp :props-a="info" @send="getChildData"></local-comp> 6 </div> 7 <script> 8 9 /*Vue.component('constComp', { 10 template: "<h3>我是全局组件</h3>" 11 12 });*/ 13 let localComp = { 14 template: "<div>我是局部组件 15 <p>父组件传过来的数据为-->{{propsA}}</p> 16 <button @click='sendMsg'>使用$emit子组件向父组件传递事件</button> 17 </div>", 18 props: { 19 propsA: { 20 type: String, 21 default: "", 22 require: true 23 } 24 }, 25 data() { 26 return { 27 msg: "子组件数据" 28 } 29 }, 30 methods: { 31 sendMsg(evt) { 32 this.$emit('send', this.msg); 33 } 34 } 35 }; 36 new Vue({ 37 el: "#app", 38 data: { 39 msg: "hello component", 40 info: "hello props", 41 fromChildData: "" 42 }, 43 components: { 44 localComp 45 }, 46 methods: { 47 getChildData(val) { 48 this.fromChildData = val 49 } 50 } 51 }); 52 53 </script>
非子父组件通信,使用一个空的Vue实例作为一个事件总线监听数据变化
这种场景用于组件之间不为子父层级关系的时候相关通信,我们使用的那个空vue实例里也可以放vue的属性。用这个空的vue实例来$emit自定义一个事件然后再用这个实例来$on监听自定义事件,从而达到非子父组件之间的通信。(PS:这里暂时不讨论vuex),看demo代码。demo例子使用了ref组件索引。
<div id="app"> {{message}} <component-a ref="a"></component-a> <component-a ref="b"></component-a> </div> <script> const bus = new Vue({}); Vue.component('component-a', { data () { return { msg: 1 } }, template: '<button @click="handleEvent">传递事件</button>', methods: { handleEvent () { bus.$emit('on-message', '来自组件 com-a 的内容'); } } }); const app = new Vue({ el: '#app', data: { message: '' }, mounted () { bus.$on('on-message', (msg) => { this.message = msg; }); this.$children.msg = 2; } }) </script>
slot组件内容分发
单个就默认slot,多个使用具名slot,$slots访问对应slot,vue2.0新增;因为vue2使用render函数来渲染,所以需要使用this.$slots来访问slot。this.$slots.xxx访问具体的slot,即slot中name指定的值 值类型{ [name: string]: ?Array<VNode> }
如果需要给slot添加默认内容的时候直接在slot上写就可以了,这个时候默认的slot内容所在的作用域就是其所在的组件实例,可以根据其所在的组件来控制slot默认的内容展示如:<slot>{{msg}}</slot>。如果没有指定默认数据的话slot内容根据其父组件所在的作用域。
当 vue 组件中当需要组件混合使用的时候需要用到内容分发,内容不确定的时候需要用到slot内容分发。
看demo代码;
1 <div id="app"> 2 <com-out> 3 <div slot="b">{{msgB}}</div> 4 <div slot="a">{{msgA}}</div> 5 6 </com-out> 7 </div> 8 <template id="co"> 9 <div> 10 hello 11 <slot name="a"></slot> 12 <slot name="b"></slot> 13 </div> 14 </template> 15 <script> 16 Vue.component('com-out', { 17 template: "#co", 18 mounted() { 19 console.log("com-out slot" + this.$slots.a[0]); 20 } 21 }); 22 new Vue({ 23 el: "#app", 24 data: { 25 msgA: '父组件数据a', 26 msgB: '父组件数据b' 27 } 28 }) 29 </script>
递归组件
递归组件要记住两点:
1.递归组件必须要给组件设置name。
2.要在一个合适的时间(条件)跳出递归否则会报栈溢出异常。
1 <div id="app"> 2 <com :count="1"></com> 3 </div> 4 <template id="cr"> 5 <div><com :count="count + 1" v-if="count < 3"></com>{{count}}</div> 6 </template> 7 <script> 8 Vue.component('com', { 9 name: 'comr', 10 template: "#cr", 11 props: { 12 count: { 13 type: Number, 14 default: 1 15 } 16 } 17 }) 18 new Vue({ 19 el: '#app' 20 }) 21 22 23 </script>
动态组件
vue动态组件其实就是在组件中使用:is属性根据值来判断显示哪个组件。
1 <div id="app"> 2 <button @click="changeCom">点击让子组件显示</button> 3 <com v-bind:is="activeCom"></com> 4 </div> 5 <script> 6 new Vue({ 7 el: '#app', 8 data: { 9 activeCom: "comA" 10 }, 11 methods: { 12 changeCom: function () { 13 let arr = ["comA", "comB", "comC"], index; 14 index = Math.ceil(Math.random()*arr.length); 15 this.activeCom = arr[index]; 16 } 17 }, 18 components: { 19 comA: { 20 template: "<div>组件comA</div>" 21 }, 22 comB: { 23 template: "<div>组件comB</div>" 24 }, 25 comC: { 26 template: "<div>组件comC</div>" 27 }, 28 } 29 }); 30 </script>
异步组件
异步组件在性能上有一定的优势,不仅加快了渲染时间也减少了不必要的加载;在路由中经常用到
看demo;
<div id="app"> <child-component></child-component> </div> <script> Vue.component('child-component', function (resolve, reject) { window.setTimeout(function () { resolve({ template: '<div>异步组件的内容</div>' }) }, 2000) }); new Vue({ el: '#app' }) </script>
在路由中异步组件可以这样用:
1 { 2 path:"/路由地址", 3 name:"routeName", 4 component: resolve => require(["../components/xxx.vue"], resolve) 5 }
组件相关属性
$nextTick 虚拟dom完成后触发回调。进行dom操作。不过相关dom操作一般都建议放在指令中,或者自己自定义指令来进行操作,用合适的方式来做合适的事效果才能达到最优。
$ref当组件使用ref来作为索引时$ref获取当前组件