深入理解vue组件
一、组件使用过程中的细节问题
1.使用 is 解决H5标签的小bug(用 is='组件名' 解决)
好处:这样的写法一方面可以使用组件,另一方面也符合H5的规范。
/*创建组件*/ Vue.component('row',{ template:'<tr><td>this is a row</td></tr>'; }); /*vue实例接管#root*/ new Vue({ el:'#root' });
<div id='root'> <table> <tbody> <row></row> <!-- 如果直接写组件名字,源代码渲染中会出错,<tr>标签会成为table的兄弟元素,--> <tr is='row'></tr> <!--正确写法:正常写<tr>标签,然后 is='row'(组件名)--> <tr is='row'></tr> </tbody> </table> </div>
2.在子组件中定义 data 时,data 必须是个有返回值的函数,不能向父组件中一样定义。
原因:子组件需要被网页中的元素多次使用,通过一个函数返回一个对象的目的,就是让每个子组件都拥有一个独立的数据存储,就不会出现多个子组件相互影响的问题。
Vue.component("row",{ //组件名row data:function(){ //函数 return { //有返回值 content:'this is a row' } }, template='<li>{{content}}</li>' //模板 });
3.获取DOM节点(vue是面向数据的开发,极少操作DOM)
用法:
1.在标签中添加 ref='ref名字' 例如:ref='hello'
2.使用Vue内置属性获取,$.refs.ref名字 例如:$refs.hello
当写在div标签中:使用 this.$refs.hello 获取的是 DOM节点
当写在组件中:使用 this.$refs.hello 获取的是子组件的引用
<div ref='dv' @click='dvClick'> ref 可以获取DOM节点 </div>
new Vue({ el:'root', methods:function(){ dvClick:function(){ console.log(this.$refs.dv); //<div> ref 可以获取DOM节点</div> } } }
4.案例:计数器
<div class="root"> <row @change='sumChange' ref='num1'></row> <!--第二步:监听子组件的触发事件,一旦触发事件,就执行sumClick方法--> <row @change='sumChange' ref='num2'></row> <!--第二步:监听子组件的触发事件,一旦触发事件,就执行sumClick方法--> <div>{{total}}</div> </div>
// 全局子组件 Vue.component('row', { data: function() { return { number: 0 } }, template: "<div @click='spanClick'>{{number}}</div>", methods: { spanClick: function() { this.number++; this.$emit("change"); //第一步:子组件向父组件触发一个名叫change的事件 } } }); // 实例组件 new Vue({ el: '.root', data: { total: 0 }, methods: { sumChange: function() { //console.log(this.$refs.num1.number);//获取子组件中number的值 this.total = this.$refs.num1.number + this.$refs.num2.number; } } });
二、父子组件的数据传递
1.父组件向子组件传递数据(属性)
父组件通过属性的方式向子组件中传递数组
<div count='0'> count后面跟的0 是字符串</div> <div :count='2'> :count后面跟着的2 是数字,因为:count后面的是表达式</div>
子组件使用props接收父组件传递过来的数据
var counter={ props:['count'], //接收父组件传递过来的数据 template:'<div>{{count}}</div>' };
注意:
有一个隐形规定:单向数据流
在vue中有单向数据流的概念:父组件可以随意向子组件传递数据,但是子组件不能修改父组件传递过来的值。
设置单向数据流,是为了防止,如果父组件传递过来的是对象形式的,在子组件中修改后,如果再被其他子组件引用,也会把其他子组件的数据一起更改了。
解决方式:
把父组件传递过来的值,赋值给子组件 data 中,找个数据接收。
var counter={ props:['count'], //接收父组件传递过来的数据 data:function(){ return { number:this.count //把父组件的值赋值给子组件的data,此时再修改,不会影响父组件中的值 } }, template:'<div @click="dvClick">{{number}}</div>', methods:{ dvClick:function(){ this.number++; } } });
2.子组件向父组件中传递数据(通过事件传值)
<div id='root'> <counter :count='3' @change='changeTotal'></counter><!--第二步:监听change事件,一旦触发,就执行changeTotal方法--> <counter :count='2' @change='changeTotal'></counter> <div>{{total}}</div> </div>
var counter={ props:['count'], data:function(){ return { number:this.count } }, template:'<div @click="dvClick">{{number}}</div>', methods:{ dvClick:function(){ this.number++; this.$emit('change',1); //第一步:每次点击时向外触发一个change事件,可以携带多个参数 } } }); new Vue({ el:'root', data:{ total:5 }, components:{ //把局部组件在实例中声明 counter:counter }, methods:{ changeTotal:function(step){ //实现子组件触发事件,父组件监听到后的处理函数 this.total+=step; } } });
三、组件参数校验与非props特性
如果 子组件 要校验 父组件 参数 类型则:
props: { //content:String //校验参数 content 是否是 String 类型 //content: [String, Number] //校验参数 content 是否为 Sting 或 Number中的一种类型 content: { type: String, //类型 required: false, //false:有没有都可以,true:必须有content这个变量 default: '默认文本内容', //默认文本内容,如果有content属性,则显示content的内容 validator: function(value) { //对传入的值校验 return value.length > 5; //true 判断值的长度是否大于5 } } }
props特性:
1.要求父组件传参数,子组件要接收,然后可以在子组件中直接使用
2.props特性,不会将属性显示在DOM的标签之中
非props特性:
1.父组件中定义了参数,子组件中没有接收,此时父组件中定义的参数就是非props特性,非props特性的内容,也就无法在子组件中使用。
2.非props特性的元素会展示在子组件最外层的DOM标签的HTML属性里面
四、给组件绑定原生事件(.native)
方式一:给子组件绑定事件,通过父组件监听触发事件
缺点:代码编写太麻烦
<child @click='childClick'></child><!--在组件中定义的事件属于自定义事件,不是原生事件--> <!--方式一:给子组件绑定事件,通过父组件监听触发事件--> <child @change='clickEvent'></child><!--监听子组件中是否触发了change-->
// 方式一:子组件绑定原生事件:直接在编写模板中绑定,通过向父组件触发事件达到效果,另外父组件中必须监听子组件的change事件有没有被触发 Vue.component=({ template:'<div @click='childClick'>此时绑定的是原生事件</div>, methods:{ childClick:function(){ this.$emit('change');//通过childClick的点击,向父组件中触发一个名叫 change 的事件 } } });
方式二:
更改监听事件的指向(.native)
<child @click.native='childClick'></child><!--此时监听的不再是子元素的自定义事件,而是父组件的原生事件-->
// 方式二:更改监听事件的指向(native) Vue.component=({ template:'<div>child</div>', }); new Vue({ el:'#app', methods:{ childClick:function(){ console.log('通过子组件的@click.native可以直接调用此方法'); } } });
五、非父子组件之间传值
也叫作Bus、总线、发布订阅模式、观察者模式
<div id='root'> <child content='Dell'></child> <child content='Lee'></child> </div>
Vue.prototype.bus=new Vue(); //vue原型属性bus,指向vue的实例,任何一个组件和实例中都有bus的属性 Vue.component({ props:{ content:String //判断接收的数据是否是String类型 }, data:{ childContent:this.content }, template:'<div @click="dvClick">{{childContent}}</div>', //定义模板 methods:{ dvClick:function(){ this.bus.$emit('change',this.childContent); //通过bus向整个vue触发一个change事件,并携带一个this.childContent的数据(此时其他子组件想使用这个数据,应该在组件中设置监听) } }, //生命周期钩子:组件被挂载的时候执行的函数 mouted:function(){ var that=this; this.bus.$on('change',function(msg){ that.content=msg; }); }); new Vue({ el:'#root' });
六、Vue中的插槽(slot)
方便向子组件中传递DOM元素
<div class='root'> <child></child><!--子组件中没有DOM结构--> </div> Vue.component=('child',{ template:`<div> <p>子组件内容</p> <slot>默认内容</slot> /* 但是模板中定义了<slot>,则会显示插槽中默认文字 */ </div>` });
显示效果:
<div class='root'> <p>子组件内容</p> 默认内容 </div
具体使用方法:
<div class='root'> <pages> <div class='header' slot='header'>header</div><!--向子组件中插入DOM节点--> <div class='footer' slot='footer'>footer</div><!--向子组件中插入DOM节点--> </pages> </div>
new Vue({ el:'.root' }); Vue.component('pages',{ template:`<div> <slot name='header'>Header</slot> //name 指定显示哪个DOM结构,和DOM中的 slot 相呼应 <div class='content'>Content</div> //原来子组件中要显示的内容 <slot name='footer'>Footer</slot> </div>` });
七、Vue中的作用域插槽
子组件模板可能在不同的地方被调用,不希望模板的样式被child给固定,希望模板样式由父组件告诉我们应该使用哪种样式
<div class='app'> <child> <template slot-scope='props'> <h1>{{props.item}}</h1> <!--下面ul中的循环内容,父元素使用 h1 标签显示--> </template> </child> </div>
/* 父组件调用子组件时,给子组件传了一个插槽(作用域插槽),作用域插槽必须是<template>开头结尾的标签 ,同时作用域插槽要声明,要从子组件中接收的数据都放在哪(props)还告诉子组件一个模板的信息,接收到props后以什么形式展示。 何时使用:当子组件做循环,或者某一部分由外部传递进来的时候,这时候就是用作用域插槽。 子组件可以向父组件的插槽中插入数据。父组件传递过来的插槽如果想接收这个数据,必须在外层使用一个template ,同时通过 slot-scote 对应的名字(props)来接收子组件传递过来的所有数据 子组件传了一个item 给父组件,在父组件的作用域插槽中就可以接收到这个item。 */ Vue.component('child',{ data:function(){ return { list:[1,2,3,4,5] } }, template:`<div> <ul> <slot v-for='item of list' :item=item ></slot> </ul> </div>` });
八、动态组件<component>与v-once
v-once:会把数据存放在内容(提高加载静态数据的速度)
template:'<div v-once>数据会被存放到内存中</div>'
动态组件:会根据 is 里面的数据的变化,自动加载不同的组件(底层在切换时,会先销毁一个组件,然后展示另外一个组件(消耗性能))
<div class='root'> <component :is='type'></component> </div>
Vue.component("child-one",{ template:"<div>child-one</div>" }); Vue.component("child-two",{ template:"<div>child-two</div>" }); new Vue({ el:'#root', data:{ type:'child-one' }, methods:{ btnClick:function(){ this.type=(this.type==='child-one'?'child-two':'child-one'); } } });