VUE中的组件
一个自定义的标签,vue就会把他看成一个组件,vue可以给这些标签赋予一定意义;一个页面就是一个组件
好处:
- 1、提高开发效率
- 2、方便重复使用
- 3、便于协同开发
- 4、更容易被管理和维护
自定义标签的书写规范:
1、组件名不支持大写字母(首字母可支持大写)
2、html中采用多个单词用-
隔开命名法,js中转换为对应的驼峰命名法
注意
:组件中的动态数据data
数据必须是函数数据类型的,返回一个实例作为组件的数据。这样做是为了防止子组件之间公用数据导致的同时更新;(下面局部组件中有案例)
全局组件
用法:声明一次可以在任何地方使用
//必须要先用component赋予自定义标签意义,然后再初始化实例 Vue.component('my-hand',{ template:'<div>{{msg}}</div>', data(){ return {msg:'我很英俊'} } }); let vm=new Vue({ el:'#app' }) //页面中使用 <div id="app"> <my-hand></my-hand> </div>
局部组件
用法:必须告诉这个组件属于谁,组件之间是相互独立的,不能直接跨作用域,vue的实例也是一个组件,拥有生命周期函数,理论上可以无限嵌套
1、创建局部组件(js)
2、注册这个组件(js)
3、在HTML中使用这个组件
注意事项:
1、子组件不能直接使用父组件的数据
2、组件理论上可以无限嵌套
3、子组件之间如果共用了数据,而且不把data写为返回一个对象的形式,会导致同时更新;
//创建一个局部组件 let handsome={ template:'<div>{{msg}}</div>', //组件中的data必须是函数类型的,返回一个值作为组件的数据 data(){ return {msg:'我很英俊'} } }; let handsome1={ template:'<div>{{msg}}</div>', data(){ return {msg:'我是在外部创建的局部组件'} } }; let vm=new Vue({ el:'#app', components:{ //在components中注册局部组件 handsome:handsome, handsome1:handsome1 //在ES6中属性名和值相同可只写一个(上面的可简写为如下的形式) handsome,handsome1, //也可直接在components中直接创建和注册子组件 handsome2:{ template:'<div>{{msg}}</div>', data(){ return {msg:'我是在components中创建和注册的局部组件'} } } } }) //在页面中使用局部组件 <div id="app"> <handsome></handsome> <handsome1></handsome1> <handsome2></handsome2> </div>
子组件之间公用数据导致的同时更新的问题;
let obj={school:'zfpx'}; let component1 = { template: `<div @click="school='hello'">组件一{{school}}</div>`, data(){ return obj;//此种写法是错误的,点击组件一的时候会导致obj的数据更新,结果也会反映到组件二上 } }; let component2 = { template: '<div>组件二{{school}}</div>', data(){ return obj; } }; let vm=new Vue({ el:'#app', components:{ component1,component2 } }) //页面中使用 <div id="app"> <component1></component1> <component2></component2> </div>
子组件与父组件之间的数据传递和嵌套
嵌套
//创建局部组件,相当于给grandson这个标签赋予了实际的意义 //一定要先创建最内层的组件(即孙子) let grandson={ template:'<div>{{msg}}</div>', data(){return {msg:'我是孙子'}} }; let son={ template:'<div>{{msg}}<grandson></grandson></div>', data(){return{msg:'我是儿子'}}, components:{ //在儿子中嵌套孙子组件 //在son的components中注册了孙子组件后,才可在son的模版中使用grandson标签(但是依然不能在页面中使用son标签) grandson } }; let parent={ template:'<div>{{msg}}<son></son></div>', data(){return{msg:'我是父亲'}}, components:{son} }; let vm=new Vue({ el:'#app', components:{ //在vue的实例中注册了parent组件之后才能在页面中使用parent标签 parent } }) //在页面中使用局部组件 <div id="app"> <parent></parent> </div>
数据传递
父传递给子
可以通过给子组件设置自定义属性的方式拿到父组件的属性值,子组件用props方法来获取到自定义属性的值就可以直接在子组件的模版中使用了;
let vm=new Vue({ el:'#app', data:{ msg:100 }, components:{ child:{ //props属性写成数组的形式不能进行校验 props:['m'],//相当于取到子组件自定义的m属性,值是父亲的 //写成对象的形式可以校验其值的类型 props:{ m:{ //校验类型拿到的值是否属于下面的类型,不属于也会进行渲染,控制台会提示类型错误 type:[Number,String,Function,Object,Array], default:0//如果子组件没有m属性,则m值默认取0 //如果m属性是必须的,则可增加required:true(不可与default同用) required:true }, }, template:'<div>儿子{{m}}</div>'//这里就可以直接用m } } }) //在页面中使用局部组件 <div id="app"> 父亲:{{msg}} <!--m属于子组件的自定义的属性,前面加了:之后,m的值就变为动态的了-> <child :m="msg"></child>//msg:拿到的是父的数据 </div>
子传递给父
为了防止子组件无意间修改了父组件的状态,props采用的是单向绑定。所以子组件传递给父组件数据就不能通过设置自定义属性的方法,而要通过发布订阅模式来传递;
let vm=new Vue({ el:'#app', data:{ money:400 }, methods:{ things(val){//val:儿子触发事件时传过来的数据 this.money=val; } }, components:{ child:{ props:['m'], //给儿子的按钮添加点击事件,当点击的时候触发发布订阅模式,执行绑定在子组件上的事件 template:'<div>儿子{{m}} <button @click="getMoney()">多要钱</button></div>', methods:{ getMoney(){//此处的this指的是子组件的实例 //触发自定义事件(child),让父亲的方法(things)执行 this.$emit('child',800); } } } } }); //在页面中使用子组件 <div id="app"> 父亲:{{money}} <child :m="money" @child="things"></child> </div>
组件中的插槽(slot标签)
slot标签的作用:定制模版
slot相当于一个插槽,可以把组件中的对应标签拿到之后替换自己里面的内容
slot中可以放一些默认的内容,如果组件中对应slot值的标签有内容则会被替换掉,如果没有则会使用slot中的默认内容
//js let model={ template:'#model' }; let vm=new Vue({ el:'#app', data:{msg:'zf'} methods:{ fn(){alert('是')} }, components:{ model } })
//html <div id="app"> <!--这里放的内容均属于父级模版的(如fn方法),所以必须在父模版的methods中写fn方法。msg属性也是父模版的。只有自定义的属性名是属于组件的(如m属性)--> <model :m="msg"> <!--下面标签中的slot值与template中的slot是对应的,没有slot值的都会被放到default中--> <a href="www.baidu.com">百度一下</a> <p slot="content">确认删除?</p> <h1 slot="title" @click="fn">是否删除?</h1> <a href="www.baidu.com">百度一下</a> </model> </div> <!--模版中只能有一个根元素--> <!--slot作用,定制模版--> <!--slot相当于一个插槽,可以把组件中的对应标签拿到之后替换自己的内容--> <!--如果组件中没有标签,则使用slot中默认的内容--> <template id="model"> <div> <slot name="title">默认标题</slot> <slot name="content">默认内容</slot> <slot name="default">这是一个默认标题</slot> </div> </template>
如何在父组件中调用子组件中的方法
通过给子组件设置ref值为xxx,在父组件中可通过
this.$refs.xxx
找到对应ref值的子组件的vue对象。(子组件的方法也挂载到了它的vue对象上)
由于在mounted执行时,子组件的DOM结构才加载完成,所以只有在mounted函数中才能用this.$refs.xxx.$el
拿到对应子组件的DOM元素;
let loading={ data(){ return{flag:true} }, template:'<div>正在加载中……</div>', //子组件的方法 methods:{ hide(){ //此方法中的this是子组件的vue对象 //hide执行的时候会把子组件的DOM元素背景色改为红色 this.$el.style.background='red'; } }, }; let vm=new Vue({ el:'#app', mounted(){ //在此处实现父组件调用子组件的方法 //此处的this是vm,this.$refs获取的不是子组件的DOM而是vue对象; // 可用this.$refs.load.$el来获取对应的DOM结构; this.$refs.load.hide(); }, components:{ loading } }) //Html <div id="app"> <loading ref="load"></loading> </div>
动态组件
component
标签
通过使用component标签,我们可以让多个组件使用同一个挂载点,并根据component内置的is
属性来实现动态切换(is属性可以理解为是哪一个组件);
let home={ template:'<div>home</div>', //如果放在了keep-alive标签中,则可以保留它的状态来避免以后点击重新渲染。只在第一次渲染的时候会弹出1 mounted(){alert(1)} }; let list={ template:'<div>list</div>', //如果放在了keep-alive标签中,则只在第一次点击的时候弹出2 mounted(){alert(2)} }; let vm=new Vue({ el:'#app', data:{radio:'home'}, components:{home,list} }) //html <div id="app"> <input type="radio" v-model="radio" value="home">home <input type="radio" v-model="radio" value="list">list <!--keep-alive标签一般用来缓存:现在讲是为了后面的路由做准备,如果缓存了,只会走beforeUpdate与之后的阶段--> <keep-alive> <component :is="radio"></component> </keep-alive> </div>
keep-alive
标签
如果想把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。可以把需要缓存的标签外面包一个 keep-alive 标签
<keep-alive> <component :is="radio"></component> </keep-alive>
$nextTick的用法
子组件和父组件都有mounted方法时,会先执行子组件的mounted方法:因为要保证子组件挂载完成再触发父组件的挂载
let vm=new Vue({ el:'#app', mounted(){//想操作最新的DOM,就需要用$nextTick方法(异步的来获取) //未加$nextTick方法,获取的不是最新的DOM console.log(this.$refs.child.$el.innerHTML);//结果为1,2,3 //为了保证操作的是最新的DOM,则需要用$nextTick方法 this.$nextTick(()=>{ console.log(this.$refs.child.$el.innerHTML);//4,5,6(获取到的是最新的结果) }) }, components:{ child:{ data(){ return {arr:[1,2,3]} }, template:'<div><li v-for="a in arr">{{a}}</li></div>', mounted(){//先执行子组件的mounted方法之后再同步执行父组件的mounted方法 this.arr=[4,5,6]//此步骤涉及到DOM渲染,所以说是异步的,而执行父组件的mounted方法时,此步骤还未实现 } } } }) //html <div id="app"> <child ref="child"></child> </div>同级组件之间的数据传递通过EventBus(不用,但要了解)
为了保证订阅和执行的是同一个对象,需要借助第三方实例,通过第三方实例实现发布订阅
EventBus就是用来创建第三方实例的,可用来保证订阅和执行的时候是同一个对象