组件化开发
-
什么是组件化:
将一个功能实现的表现,结构,行为作一个整体来封装,形成一个组件,达到整体效果复用的目的。
-
Vue组件的注册方式:
- 根组件:
- 当前我们在开始使用vue的时候,就创建了一个组件,new Vue()实例化的过程 ,就是创建了一个vue的根组件,运行new Vue的文件就是vue项目中的根组件。
- 注册组件(自定义组件)
<div id="box"> <!-- 使用组件navbar --> <navbar></navbar> </div> <script> // 注册组件navbar Vue.component("navbar",{ //template:定义组件模板 template : ` <div style="background-color: #cccccc;"> <span @click="main()">{{ getMain }}</span>| <span>home</span>| <span>aboult</span> </div> `, methods:{ main(){ console.log("回到首页") } }, computed:{ getMain(){ return "首页" } } }) var vm = new Vue({ el:"#box" }) </script>
程序说明:
上面只是一个最基础的组件的注册和使用,这里有如下问题需要说明
1,自定义的组件必须在根组件环境下才能运行。
2,组件取名问题:component注册的组件名称如果是以驼峰的形式,如:navBar,那么在html中使用该组件时,必须书写成
3, template定义的html片段:没有代码提示,没有高亮,会造书写容易出差,难以查错的情况,后续有解决办法
4,css目前只能写成行内形式,后续有解决办法
5,template片段只能包含一个根节点。否则会报错。
6,组件是独立存在的,当前component内部的程序无法直接访问外部的模型状态或方法,将来可以通过组件之间通讯来解决
7,组件中的数据模型必须定义在 data(){}函数中,data函数必须有return返回,如下:
Vue.component("navbar",{ template : ` <div style="background-color: #cccccc;"> <span @click="main()">{{ myMain }}</span>| <span>home</span>| <span>aboult</span> </div> `, methods:{ main(){ console.log("回到首页") } }, data(){// return { myMain:"main" } } })
-
全局组件:
Vue.component():方法注册的组件都是全局组件。
<div id="box"> <!-- 页面中使用全局组件 --> <navbar></navbar> <tabbar></tabbar> </div> <script> //全局组件navbar Vue.component("navbar",{ template : ` <div style="background-color: #cccccc;"> <tabbar></tabbar> navbar 这里也可以使用了tabbar </div> ` }) //全局组件tabbar Vue.component("tabbar",{ template:` <div style="background:blue;"> tabbar </div> ` }) var vm = new Vue({ el:"#box" }) </script>
- 注册局部组件
全局组件tabbar Vue.component("tabbar",{ //局部组件只能在当前组件下使用 template:` <div style="background:blue;"> tabbar <tabbarChild></tabbarChild> </div> `, //定义局部组件 components:{ "tabbarChild":{//局部组件名称 //局部组件模板 template:`<p>这是一个局部组件</p>` } } })
- 根组件:
-
父子组件之间的通信传值
- 父传子:
<div id="box"> <!-- 父组件范围使用组件 --> <!-- 向子组件中传递的值有:home,detail,cart,flag,true,false --> <tabbar :text="home" :show-home="flag"></tabbar> <tabbar :text="detail" show-home="true"></tabbar> <!-- 没有添加show-home属性,默认设置为true --> <tabbar :text="cart"></tabbar> </div> <script> Vue.component("tabbar",{ template:` <div style="background:blue;color:white;"> <button v-if="showHome">返回</button> <span>导航--{{text}}</span> <button>登录</button> </div> `, //props:["text","showHome"]//子组件通过自定义的行内属性获取父组件中传递过来的值 // props:{ // text:String,//指定接收的数据类型 // showHome:Boolean//指定接收的数据类型是一个布尔值,如果传递过来的不是布尔值,程序会报错,提示错误原因 // } props:{//完整写法 text:{ type:String, default:"默认值" }, showHome:{ type:Boolean, default:true } } }) //根组件:tabbar组件的父组件 var vm = new Vue({ el:"#box", data:{ //父组件中的数据 home:"首页", detail:"详情", cart:"购物车", flag:false } }) </script>
- 子传父
<div id="box"> <!-- 使用子组件 --> <!-- 自定义事件myEvent,事件处理程序为getData,该处理程序要在父组件中定义 --> <child @myevent="getData"></child> </div> <script> Vue.component("child",{ template:` <div style="background:blue;color:white;"> 子组件--<button @click="sendData">点击传递数据给父组件</button> </div> `, data(){ return { //子组件中的模型状态 childData:"1000000" } }, methods:{ sendData(){//点击组件中的button后,会触发该处理程序 //事件分发机制 this.$emit,方法可以触发组件使用时的自定义事件 this.$emit("myevent",this.childData); } } }) //根组件 var vm = new Vue({ el:"#box", methods:{ //父组件中定义的方法,用于接收子组件传递过来的数据 getData(data){ //data参数保存了组件传递过来的数据 console.log("子组件中的数据:"+data); } } }) </script>
-
子传父:this.$refs:
- vue中可以通过this.$refs获取页面中所有定义了ref行内属性的元素对象(DOM节点对象)
<div id="box"> <!-- 元素定义了ref行内属性,ref属性可以在任意的元素上绑定 --> <input type="text" ref="myinput"> <div ref="mycontainer">container</div> <button @click="handleClick">click</button> </div> <script> //根组件 var vm = new Vue({ el:"#box", methods:{ handleClick(){ //通过this.$refs属性可以获取页面中所有绑定ref行内属性的DOM对象 console.log(this.$refs) } } }) </script>
- 通过this.$refs属性获取子组件对象
<div id="box">
<!-- 子组件上定义ref属性 -->
<child ref="myChild"></child>
<button @click="handleClick">获取子组件</button>
</div>
<script>
Vue.component("child",{
template:`<div>child</div>`,
data(){
return{
msg:"哈哈"
}
}
})
//根组件
var vm = new Vue({
el:"#box",
methods:{
handleClick(){
//通过this.$refs属性可以直接获取子组件对象
console.log(this.$refs.myChild)
}
}
})
</script>
-
非父子组件之间的通信
- 中间人模式:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../js/vue.js"></script> <style> *{ margin: 0; padding: 0; } .goodWarmp{ width: 500px; display: flex; justify-content:left; border: 1px dashed black; } .goodWarmp>div:last-child{ text-align: left; } .detail{ width: 200px; height: 200px; background-color: #cccccc; position: fixed; right: 10px; top: 100px; border: 2px solid black; } </style> </head> <body> <div id="box"> <button @click="handleAjax">ajax</button> <!-- 使用组件,显示商品列表 :mydata:自定义属性,将数据传递给子组件 @myevent="midHandle":自定义事件,用于处理子组件传递过来的数据 --> <film-item v-for="(item,index) in dataList" :key="item.id" :mydata="item" @myevent="midHandle"></film-item> <!-- 使用组件显示详情信息 detailmsg:接收从filmItem组件传递过来的数据 --> <show-detail :detailmsg="midData"></show-detail> </div> <script> //创建组件filmItem,用于渲染商品列表界面 Vue.component("filmItem",{ template : ` <div class="goodWarmp"> <div> <img :src="mydata.img"/> </div> <div> <h2>{{mydata.nm}}</h2> <p> 电影评分:<span>{{mydata.sc}}</span> </p> <p> 主要演员:<span>{{mydata.star}}</span> </p> <p> <button @click="getDetail">详情</button> </p> </div> </div> `, //通过组件中的自定义属性mydata接收父组件传递过来的数据 props : ["mydata"], methods : { getDetail(){//点击获取详情信息 //详情信息,需要传递到非父子组件showDetail中 //先传递给父组件,再由父组件传递给showDetail,父组件类似于一个中转站,起到了一个中间人的作用 this.$emit("myevent",this.mydata.showInfo); } } }) //创建详情显示组件 Vue.component("showDetail",{ template : ` <div class="detail">{{detailmsg}}</div> `, props : ['detailmsg']//接收filmItem的详情信息 }) var vm = new Vue({ el : "#box", data : { //保存商品列表数据 dataList : [], //保存中间人传递的数据 midData : "" }, methods : { handleAjax(){ fetch("../data/maoyan.json") .then(res=>res.json()) .then(res=>{ //请求获取数据 console.log(res.movieList) //将数据保存在模型状态下 this.dataList = res.movieList }) }, midHandle(detail){//中间人函数 console.log(detail); this.midData = detail } } }) </script> </body> </html>
-
bus通信:
创建一个新的Vue实例,通过实例下的 e m i t ( ) 方 法 通 过 事 件 分 发 的 形 式 派 发 一 个 自 定 义 事 件 , 通 过 实 例 下 的 、 emit()方法通过事件分发的形式派发一个自定义事件,通过实例下的、 emit()方法通过事件分发的形式派发一个自定义事件,通过实例下的、on()方法可以监听到$emit分发的事件,达到数据传递的目的
bus.$emit(event,data)//发布
event:要发布的事件
data:要发布的信息数据
bus.$on(event,(data)=>{//监听
event:监听的事件
data:发布的对应数据
})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../js/vue.js"></script> <style> *{ margin: 0; padding: 0; } .goodWarmp{ width: 500px; display: flex; justify-content:left; border: 1px dashed black; } .goodWarmp>div:last-child{ text-align: left; } .detail{ width: 200px; height: 200px; background-color: #cccccc; position: fixed; right: 10px; top: 100px; border: 2px solid black; } </style> </head> <body> <div id="box"> <child1></child1> <child2></child2> </div> <script> //创建中央事件总线 bus var bus = new Vue(); Vue.component("child1",{ template : ` <div>child1-<button @click="busClick">发布详细信息</button></div> `, methods : { busClick(){ //分发一个自定义事件busEvent,并发布了信息数据 bus.$emit("busEvent",{name:"tom",age:19}); } } }) Vue.component("child2",{ template : ` <div> <p>child2-订阅组件</p> <p v-if="msg">{{msg.name}} | {{msg.age}}</p> </div> `, mounted(){//生命周期函数,函数触发时机:DOM渲染完毕后就会执行的函数。 console.log("执行了") //监听busEvent事件的分发,从而通过回调获取发布的信息数据 bus.$on("busEvent",(data)=>{ console.log("获取到详情了",data); //保存数据模型状态 this.msg = data }); }, data(){ return { msg : null } } }) var vm = new Vue({ el : "#box" }) </script> </body> </html>
- bus通信实现中间人模式中的案例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../js/vue.js"></script> <style> *{ margin: 0; padding: 0; } .goodWarmp{ width: 500px; display: flex; justify-content:left; border: 1px dashed black; } .goodWarmp>div:last-child{ text-align: left; } .detail{ width: 200px; height: 200px; background-color: #cccccc; position: fixed; right: 10px; top: 100px; border: 2px solid black; } </style> </head> <body> <div id="box"> <button @click="handleAjax">ajax</button> <!-- 使用组件,显示商品列表 :mydata:自定义属性,将数据传递给子组件 --> <film-item v-for="(item,index) in dataList" :key="item.id" :mydata="item"></film-item> <show-detail></show-detail> </div> <script> var bus = new Vue();//新实例 //创建组件filmItem,用于渲染商品列表界面 Vue.component("filmItem",{ template : ` <div class="goodWarmp"> <div> <img :src="mydata.img"/> </div> <div> <h2>{{mydata.nm}}</h2> <p> 电影评分:<span>{{mydata.sc}}</span> </p> <p> 主要演员:<span>{{mydata.star}}</span> </p> <p> <button @click="emitDetail">详情</button> </p> </div> </div> `, //通过组件中的自定义属性mydata接收父组件传递过来的数据 props : ["mydata"], methods : { emitDetail(){//发布信息 bus.$emit("myevent",this.mydata.showInfo); } } }) //创建详情显示组件 Vue.component("showDetail",{ template : ` <div class="detail">{{detail}}</div> `, mounted(){ bus.$on("myevent",data=>{ //保存监听到的信息 this.detail = data }) }, data(){ return { detail : "请点击查看详情" } } }) var vm = new Vue({ el : "#box", data : { //保存商品列表数据 dataList : [], }, methods : { handleAjax(){ fetch("../data/maoyan.json") .then(res=>res.json()) .then(res=>{ //请求获取数据 console.log(res.movieList) //将数据保存在模型状态下 this.dataList = res.movieList }) } } }) </script> </body> </html>
-
v-model通信
v-model语法糖,可以直接实现组件之间的数据传递
v-model用于组件实例上,在组件内部可以直接通过props关联上一个value,
组件内部的input元素需要注册一个input事件,并在处理函数中通过分发input事件将数据再次发布出去。
<div id="box"> <!-- v-model绑定一个uname状态 父组件中的uname模板状态可以接收到子组件中input的value值 --> <my-input type="text" title="姓名" v-model="uname"></my-input> <my-input type="password" title="密码" v-model="pass"></my-input> <my-input type="number" title="电话" v-model="phone"></my-input> <button type="submit" @click="registerClick">register</button> <button type="reset" @click="clearClick">reset</button> </div> <script> Vue.component("myInput",{ template : ` <div> <label>{{title}}</label> <input :type="type" @input="handleInput" :value="value"/> </div> `, //props直接绑定value,可以与组件实例中的属性v-model关联 props : ["type","title","value"], methods : { handleInput(eve){ //console.log(eve.target.value); //分发一个input事件,将改变后的value传递出去 //父组件因为v-model的双向数据绑定关系,可以直接获取到对应的内容 this.$emit("input",eve.target.value); } } }) var vm = new Vue({ el : "#box", data : { uname : "", pass : "", phone : 0 }, methods : { registerClick(){//点击获取所有字段的内容 console.log(this.uname,this.pass,this.phone); }, clearClick(){ this.uname = "", this.pass = "", this.phone = "" } } }) </script>
-
动态组件
- 方案一:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../js/vue.js"></script> <style> *{ margin: 0; padding: 0; } *{ padding: 0; margin: 0; } body{ width: 100%; height: 100%; } footer{ height: 30px; width: 100%; background-color: #cccccc; position: fixed; left: 0; bottom: 0; } footer ul{ list-style: none; height: 100%; display: flex; justify-content: space-between; } footer ul li{ text-align: center; width: 33%; line-height: 30px; } </style> </head> <body> <div id="box"> <!-- 当isWhitch状态为1时,显示home组件页面 --> <home v-show="isWhitch === 1"></home> <!-- 当isWhitch状态为2时,显示detail组件页面 --> <detail v-show="isWhitch === 2"></detail> <!-- 当isWhitch状态为3时,显示cart组件页面 --> <cart v-show="isWhitch === 3"></cart> <footer> <ul> <!-- 点击时修改isWhitch状态值,以些决定显示哪个组件作为当前页面显示 --> <li @click=" isWhitch = 1 ">首页</li> <li @click=" isWhitch = 2 ">详情</li> <li @click=" isWhitch = 3 ">购物车</li> </ul> </footer> </div> <script> Vue.component("home",{ template : `<div>home</div>` }) Vue.component("detail",{ template : `<div>detail</div>` }) Vue.component("cart",{ template : `<div>cart</div>` }) var vm = new Vue({ el : "#box", data : { isWhitch : 1//定义初始值 } }) </script> </body> </html>
- 方案二,使用vue中的自定义component标签
- 标签是Vue框架自定义的标签,它的用途就是可以动态绑定我们的组件,根据数据的不同更换不同的组件
<div id="box"> <!-- :is指令,绑定模型状态,当状态改变时,会自动切换到对应的组件显示 --> <component :is="isWhitch"></component> <footer> <ul> <!-- 点击时修改isWhitch状态值,以些决定显示哪个组件作为当前页面显示 --> <li @click=" isWhitch = 'home' ">首页</li> <li @click=" isWhitch = 'detail' ">详情</li> <li @click=" isWhitch = 'cart' ">购物车</li> </ul> </footer> </div> <script> Vue.component("home",{ template : `<div>home</div>` }) Vue.component("detail",{ template : `<div>detail</div>` }) Vue.component("cart",{ template : `<div>cart</div>` }) var vm = new Vue({ el : "#box", data : { isWhitch : 'home'//定义初始值 } })
- 在动态绑定组件时,使用keep-alive标签,可以保持组件的实时状态
如:
<div id="box"> <!-- :is指令,绑定模型状态,当状态改变时,会自动切换到对应的组件显示 --> <!-- 使用keep-alive标签可以保证在切换组件页面时,不会破坏用户在原组件的输入状态 --> <keep-alive> <component :is="isWhitch"></component> </keep-alive> <footer> <ul> <!-- 点击时修改isWhitch状态值,以些决定显示哪个组件作为当前页面显示 --> <li @click=" isWhitch = 'home' ">首页</li> <li @click=" isWhitch = 'detail' ">详情</li> <li @click=" isWhitch = 'cart' ">购物车</li> </ul> </footer> </div> <script> Vue.component("home",{ template : `<div> home <input type="text"/> </div>` }) Vue.component("detail",{ template : `<div>detail</div>` }) Vue.component("cart",{ template : `<div>cart</div>` }) var vm = new Vue({ el : "#box", data : { isWhitch : 'home'//定义初始值 } }) </script>
-
组件插槽slot(内容分发)
混合父组件的内容与子组件自己的模板–>内容分发
父组件模板的内容在父组件作用域内编译,子组件模板的内容在子组件作用域内编译
- 在一个组件实例中 ,无法插入其它元素,插入的元素不会被渲染,会被组件内部的元素覆盖
<div id="box"> <child> <!-- 组件实例中使用的元素不会被渲染 --> <div>11111111111</div> </child> </div> <script> Vue.component("child",{ // 内部的元素会覆盖组件实例中插入的元素 template:` <div>child</div> ` }) var vm = new Vue({ el : "#box", data : {} }) </script>
- 如果要在组件实例中插入其它元素并被成功渲染,需要使用组件插槽slot;
<div id="box">
<child>
<!-- 这里插入的元素将全部被插入到slot插槽中 -->
<div>11111111111</div>
<div>22222222222</div>
<div>33333333333</div>
</child>
</div>
<script>
Vue.component("child",{
// 使用slot插槽,组件实例中的所有元素都将插入到这个slot插槽中
// 有多个slot,则将会被对应的插入多少次。
template:`
<div>
<slot></slot>
child
<slot></slot>
</div>
`
})
var vm = new Vue({
el : "#box",
data : {}
})
</script>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uAaddpSv-1630632678351)(/images/slot2.png)]
- 我们可以将元素插入到指定的插槽中
- slot插槽应用
- 下面看一个简单的导航案例
<div id="box">
<navbar @myevent="isShow=!isShow"></navbar>
<sidebar v-show="isShow"></sidebar>
</div>
<script>
Vue.component("navbar",{
template:`
<div style="background:skyblue">
导航栏-<button @click="handleClick">click</button>
</div>
`,
methods:{
handleClick(){
this.$emit("myevent")
}
}
})
Vue.component("sidebar",{
template:`
<div>
<ul style="width:100px;background:#ccc;">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
`
})
var vm = new Vue({
el : "#box",
data : {
isShow:false
}
})
</script>
通过分析上面的案例,我们发现,导航点击的组件中,需要通过事件分发的方式,才能处理父组件中的isShow状态值,使用插槽进行整改:可以将button按钮提取到父组件范围中插入,在父组件中,button按钮可以直接操作父组件下的isShow,无需通过子父组件之间传值方式来进行操作,使得程序变得既简洁又合理。
<div id="box">
<navbar>
<!-- 这里的按钮可以直接操作父组件中的模型状态 -->
<button @click="isShow=!isShow">click</button>
</navbar>
<sidebar v-show="isShow"></sidebar>
</div>
<script>
Vue.component("navbar",{
//组件中使用slot插槽来存放实例中的按钮
template:`
<div style="background:skyblue">
导航栏-<slot></slot>
</div>
`
})
Vue.component("sidebar",{
template:`
<div>
<ul style="width:100px;background:#ccc;">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
`
})
var vm = new Vue({
el : "#box",
data : {
isShow:false
}
})
</script>
- slot的新用法 v-slot:name:必须使用在template标签上
<div id="box">
<navbar>
<template v-slot:left>
<div>
<span><</span>
<span>返回</span>
</div>
</template>
<template v-slot:middle>
<div>
标题
</div>
</template>
<template v-slot:right>
<div>
<img width="25px" src="seach.png" alt="">
</div>
</template>
</navbar>
</div>
<script>
Vue.component("navbar",{
//组件中使用slot插槽来存放实例中的按钮
template:`
<div style="display:flex;justify-content: space-around;">
<slot name="left"></slot>
<slot name="middle"></slot>
<slot name="right"></slot>
</div>
`
})
var vm = new Vue({
el : "#box",
data : {}
})
</script>
v-slot
还可以使用简写:
我们知道,vue中,一些指令有其简写,如 v-bind:属性
的简写为:属性
,v-on:事件
的简写为@事件
v-slot:name
的简写为#name
<div id="box">
<navbar>
<template #left>
<div>
<span><</span>
<span>返回</span>
</div>
</template>
<template #middle>
<div>
标题
</div>
</template>
<template #right>
<div>
<img width="25px" src="seach.png" alt="">
</div>
</template>
</navbar>
</div>