一、组件化开发概述
1.组件化开发思想
标准
分治:不同的功能分配到不同的组件中
重用:
组合
2.编程中的组件化思想体现
3.组件化规范:
Web Components
我们希望尽可能多的重用代码
自定义组件的方式不太容易(html、css和js)
多次使用组件可能导致冲突
Web Components通过创建封装好功能的定制元素解决上述问题
Vue部分实现了上述规范
不同的功能封装到不同的组件中,组件可以组合以形成完整的应用
二、组件的注册
2.1 全局组件注册语法
Vue.component(组件名称,{
data:组件数据,
template:组件模板内容
})
例:
1 // 定义一个名为button-counter的组件 2 Vue.component('button-counter',{ 3 data:function(){ 4 return{ 5 count:0 6 } 7 }, 8 template:`<button @click='count++'>点击了{{count}}次</button>` 9 });
2.2 组件的用法
1 <div id="app"> 2 <button-counter></button-counter> 3 <button-counter></button-counter> 4 <button-counter></button-counter> 5 </div>
注:组件可以重用,且组件之间是相互独立的
2.3 组件注册注意事项
1.data必须是一个函数
分析函数与普通对象的对比
2.组件模板内容必须是单个的根元素
分析实际演示效果
3.组件模板内容可以是模板字符串
模板字符串需要浏览器提供支持(ES6语法)
4.组件的命名方式
短横线
Vue.component('my-component',{/*...*/})
驼峰式
Vue.component('MyComponent',{/*...*/})
如果使用驼峰式命名组件,那么在使用组件的时候,只能在字符串模板中用驼峰的方式使用组件,但是在普通的标签模板中,必须使用短横线的方式使用组件
2.4 局部组件注册
var ComponentA = { /* ... */}
var ComponentB = { /* ... */}
var ComponentC = { /* ... */}
new Vue({
el:'#app',
components:{
'component-a':ComponentA,
'component-b':ComponentB,
'component-c':ComponentC,
}
})
1 var HelloWorld = { 2 data:function(){ 3 return{ 4 msg:'Hello World' 5 } 6 }, 7 template:`<div>{{msg}}</div>` 8 }; 9 10 var HelloTom = { 11 data:function(){ 12 return{ 13 msg:'Hello Tom' 14 } 15 }, 16 template:`<div>{{msg}}</div>` 17 }; 18 19 var HelloJerry = { 20 data:function(){ 21 return{ 22 msg:'Hello Jerry' 23 } 24 }, 25 template:`<div>{{msg}}</div>` 26 }; 27 var vm = new Vue({ 28 el:'#app', 29 data:{ 30 31 }, 32 components:{ 33 'hello-world':HelloWorld, 34 'hello-tom':HelloTom, 35 'hello-jerry':HelloJerry 36 } 37 })
注:局部组件只能在父组件中使用,不能在其他的组件中使用
三、Vue调试工具
3.1 调试工具安装
(1) 克隆仓库
(2) 安装依赖包
(3) 构建
(4) 打开Chrome扩展页面
(5) 选中开发者模式
(6) 加载已解压的扩展,选择shells/chrome
四、组件间数据交互
4.1 父组件向子组件传值
1.子组件中通过props接收父组件传递过来的内容,具体通过属性名来接收
2.父组件通过属性将值传递给子组件,属性绑定有两种方式,一是写死,二是动态绑定
3.属性可以传递多个,具体可以在props中多加参数
1 <div id="app"> 2 {{pmsg}} 3 <menu-item title='来自父组件的值'></menu-item> 4 <menu-item :title='ptitle' content='hello'></menu-item> 5 </div>
1 Vue.component('menu-item',{ 2 props:['title','content'], 3 data:function(){ 4 return{ 5 msg:'子组件自身的数据' 6 } 7 }, 8 template:`<div>{{msg + '----' + title + '----' + content}}</div>` 9 }) 10 var vm = new Vue({ 11 el:'#app', 12 data:{ 13 pmsg:'父组件中的内容', 14 ptitle:'父组件传向子组件的动态绑定的数据' 15 } 16 })
4.2 props属性名规则
1.在props中使用驼峰的形式,模板中需要使用短横线的形式
2.字符串形式的模板中没有这个限制
1 <div id="app"> 2 {{pmsg}} 3 <menu-item :menu-title='ptitle'></menu-item> 4 </div>
1 Vue.component('third-com',{ 2 props:['testTitle'], 3 template:`<div>{{testTitle}}</div>` 4 }) 5 Vue.component('menu-item',{ 6 props:['menuTitle'], 7 template:`<div>{{menuTitle}}<third-com testTitle="啦啦啦"></third-com></div>` 8 }) 9 var vm = new Vue({ 10 el:'#app', 11 data:{ 12 pmsg:'父组件中的内容', 13 ptitle:'动态绑定属性' 14 }, 15 })
4.3 props属性值类型
字符串 String
数值 Number
布尔值 Boolean
数组 Array
对象 Object 1<div id="app"> 2<!--
3 字符串 String 4 数值 Number 5 布尔值 Boolean 6 数组 Array 7 对象 Object 8 --> 9 <menu-item :pstr='pstr' :pnum='pnum' :pboo='pboo' :parr='parr' :pobj='pobj'></menu-item> 10 </div>
1 Vue.component('menu-item',{ 2 props:['pstr','pnum','pboo','parr','pobj'], 3 template:`<div> 4 <div>{{"传递的是字符串" + pstr}}</div> 5 <div>{{"传递的是数字" + pnum}}</div> 6 <div>{{"传递的是布尔值" + pboo}}</div> 7 <div>数组:</div> 8 <ul> 9 <li :key='index' v-for='(item,index) in parr'>{{item}}</li> 10 </ul> 11 <div>{{"传递的是对象:"}}<span>{{pobj.name}}</span><span>{{pobj.age}}</span></div> 12 </div>` 13 }) 14 var vm = new Vue({ 15 el:'#app', 16 data:{ 17 pmsg:'父组件中的内容', 18 pstr:'hello', 19 pnum:12, 20 pboo:true, 21 parr:['apple','orange','banana'], 22 pobj:{ 23 name:'lisi', 24 age:12 25 } 26 } 27 })
4.4 子组件向父组件传值
注:props传递数据原则: 单向数据流,只允许父组件向子组件传递数据,不允许子组件直接操作props的数据
1. 子组件通过自定义事件向父组件传值
<button v-on:click='$emit("enlarge-text")'>扩大字体</button>
2. 父组件监听子组件事件
<menu-item v-on:enlarge-text='fontSize += 0.1'></menu-item>
1 <div id="app"> 2 <div :style='{fontSize:fontSize + "px"}'>{{pmsg}}</div> 3 <menu-item :parr='parr' @enlarge-text='handle'></menu-item> 4 </div>
1 /* 2 props传递数据原则: 单向数据流,只允许父组件向子组件传递数据,不允许子组件直接操作props的数据 3 */ 4 Vue.component('menu-item',{ 5 props:['parr'], 6 template:` 7 <div> 8 <ul> 9 <li :key='index' v-for='(item,index) in parr'>{{item}}</li> 10 </ul> 11 <button @click='parr.push("lemon")'>点击</button> 12 <button @click='$emit("enlarge-text")'>扩大父组件中字体大小</button> 13 </div> 14 ` 15 }) 16 var vm = new Vue({ 17 el:'#app', 18 data:{ 19 pmsg:'父组件中内容', 20 parr:['apple','orange','banana'], 21 fontSize:10 22 }, 23 methods: { 24 handle:function(){ 25 // 扩大字体大小 26 this.fontSize += 5; 27 } 28 }, 29 })
3. 子组件通过自定义事件向父组件传递消息
<button v-on:click='$emit("enlarge-text",0.1)'>扩大字体</button>
4. 父组件监听子组件的事件
<menu-item v-on:enlarge-text='fontSize += $event'></menu-item>
$event是固定的
1 <div id="app"> 2 <div :style='{fontSize:fontSize + "px"}'>{{pmsg}}</div> 3 <menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item> 4 </div>
1 Vue.component('menu-item',{ 2 props:['parr'], 3 template:` 4 <div> 5 <ul> 6 <li :key='index' v-for='(item,index) in parr'>{{item}}</li> 7 </ul> 8 <button @click='parr.push("lemon")'>点击</button> 9 <button @click='$emit("enlarge-text",5)'>扩大父组件中字体大小</button> 10 <button @click='$emit("enlarge-text",10)'>扩大父组件中字体大小</button> 11 </div> 12 ` 13 }) 14 var vm = new Vue({ 15 el:'#app', 16 data:{ 17 pmsg:'父组件中内容', 18 parr:['apple','orange','banana'], 19 fontSize:10 20 }, 21 methods: { 22 handle:function(val){ 23 // 扩大字体大小 24 this.fontSize += val; 25 } 26 }, 27 })
4.5 非父子组件间传值
1. 单独的事件中心管理组件间的通信
var eventHub = new Vue()
2. 监听事件与销毁事件
eventHub.$on('add-todo',addTodo)
eventHub.$off('add-todo')
3. 触发事件
eventHub.$emit('add-todo',id)
1 <div id="app"> 2 <div>父组件</div> 3 <div><button @click='handle'>销毁事件</button></div> 4 <test-tom></test-tom> 5 <test-jerry></test-jerry> 6 </div>
1 // 提供事件中心 2 var hub = new Vue(); 3 4 Vue.component('test-tom',{ 5 data:function(){ 6 return{ 7 num:0 8 } 9 }, 10 template:` 11 <div> 12 <div>TOM:{{num}}</div> 13 <div> 14 <button @click='handle'>点击</button> 15 </div> 16 </div> 17 `, 18 methods: { 19 handle:function(){ 20 // 触发兄弟组件的事件 21 hub.$emit('jerry-event',2); 22 } 23 }, 24 mounted:function(){ 25 // 监听事件 26 hub.$on('tom-event',(val) =>{ 27 this.num += val; 28 }); 29 } 30 }); 31 Vue.component('test-jerry',{ 32 data:function(){ 33 return{ 34 num:0 35 } 36 }, 37 template:` 38 <div> 39 <div>JERRY:{{num}}</div> 40 <div> 41 <button @click='handle'>点击</button> 42 </div> 43 </div> 44 `, 45 methods: { 46 handle:function(){ 47 // 触发兄弟组件的事件 48 hub.$emit('tom-event',1); 49 } 50 }, 51 mounted:function(){ 52 // 监听事件 53 hub.$on('jerry-event',(val) =>{ 54 this.num += val; 55 }); 56 } 57 }); 58 59 var vm = new Vue({ 60 el:'#app', 61 data:{ 62 63 }, 64 methods: { 65 handle:function(){ 66 hub.$off('tom-event'); 67 hub.$off('jerry-event'); 68 } 69 }, 70 })
五、组件插槽
5.1 组件插槽的作用
父组件向子组件传递内容
<alert-box>hi</alert-box> -> <slot></slot>
5.2 组件插槽的基本用法
1.插槽的位置
Vue.component('alert-box',{
template:`
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
2.插槽的位置
<alert-box>Something bad happened</alert-box>
1 <div id="app"> 2 <alert-box>啦啦啦</alert-box> 3 <alert-box></alert-box> 4 </div>
1 Vue.component('alert-box',{ 2 template:` 3 <div> 4 <strong>ERROR:</strong> 5 <slot>默认内容</slot> 6 </div> 7 ` 8 }) 9 var vm = new Vue({ 10 el:'#app', 11 data:{ 12 13 } 14 })
5.3 具名插槽用法
1.插槽定义
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
2.插槽内容
<base-layout>
<h1 slot="header">标题内容</h1>
<p>主要内容1</p>
<p>主要内容2</p>
<p slot="footer">底部内容</p>
</base-layout>
5.4 作用域插槽
应用场景:父组件对子组件内容进行加工处理
六、基于组件的案例 - 购物车
6.1 按照组件化的方式实现业务需求
根据业务功能进行组件化划分
(1) 标题组件 展示文本
(2) 列表组件 列表展示、商品数量变更、商品删除
(3) 结算组件 计算商品总额
实现整体布局和样式效果
划分独立的功能组件
组合所有的子组件形成整体结构
逐个实现各个组件功能
标题组件
列表组件
结算组件