一. 组件化开发思想
1.1 现实中的组件化思想体现
- 标准
- 分治
- 重用
- 组合
1.2 编程中的组件化思想体现
1.3 组件化规范: Web Components
- 我们希望尽可能多的重用代码
- 自定义组件的方式不太容易(html、css和js)
- 多次使用组件可能导致冲突
Web Components 通过创建封装好功能的定制元素解决上述问题
规范网址:https://developer.mozilla.org/zh-CN/docs/Web/Web_Components
Vue部分实现了上述规范
二. 组件注册
2.1 全局组件注册语法
Vue.component(组件名称, {
data: 组 件 数 据 ,
template: 组件模板内容
})
案例:
// 注册一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0;
}
},
template: '<button v-on:click="count++">点击了{{ count }}次.</button>'
})
2.2 组件用法
<div id="app">
<button-counter></button-counter>
</div>
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
2.3 组件注册注意事项
1.data必须是一个函数
- 分析函数与普通对象的对比
2.组件模板内容必须是单个跟元素
- 分析演示实际的效果
3.组件模板内容可以是模板字符串
- 模板字符串需要浏览器提供支持(ES6语法
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件注册</title>
</head>
<body>
<div id="app">
<!-- 组件可以重用,每个组件都是独立的实例,且每个组件的数据都是独立的,相互不影响。 -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script src="./js/vue.js"></script>
<script type="text/javascript">
Vue.component('button-counter', {
data: function() {
// 这里构成了一个闭包的环境,使每个组件的数据都是独立的。
return {
count: 0
}
},
// 组件模板内容必须是单个跟元素
// template: `
// <button @click="handle">点击了{{count}}次</button>
// <button>按钮</button>
// `, ------错误
// template: `
// <div>
// <button @click="handle">点击了{{count}}次</button>
// <button>按钮</button>
// </div>`, ------正确
// 组件模板内容可以是模板字符串
template: `
<div>
<button @click="handle">点击了{{count}}次</button>
<button>测试</button>
</div>
`,
methods: {
handle: function() {
this.count += 2;
}
}
});
// 其实下列实例对象也是一个组件
var vm = new Vue({
el: '#app',
data: {},
});
</script>
</body>
</html>
4. 组件命名方式
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件注册</title>
</head>
<body>
<div id="app">
<button-counter></button-counter>
<hello-world></hello-world>
</div>
<script src="./js/vue.js"></script>
<script type="text/javascript">
// 注意
// 如果使用驼峰式命名组件,那么在使用组件的时候,只能在字符串模板中用驼峰的方法使用组件,
// 但是在普通的标签模板中,必须使用短横线的方法只用组件。
// 驼峰方式
Vue.component('HelloWorld', {
data: function() {
return {
msg: 'Good Vue',
};
},
template: `<div>{{msg}}</div>`
});
// 短横线方式
Vue.component('button-counter', {
data: function() {
return {
count: 0
}
},
template: `
<div>
<button @click="handle">点击了{{count}}次</button>
<button>测试</button>
<HelloWorld></HelloWorld>
</div>
`,
methods: {
handle: function() {
this.count += 2;
}
}
});
var vm = new Vue({
el: '#app',
data: {},
});
</script>
</body>
</html>
2.4 局部组件注册
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>定义局部组件</title>
</head>
<body>
<div id="app">
<hello-vue></hello-vue>
<hello-duan></hello-duan>
<hello-js></hello-js>
<!-- <text-box></text-box> -->
</div>
<script src="./js/vue.js"></script>
<script type="text/javascript">
// Vue.component('text-box', {
// template: `
// <div>
// <span>text</span>
// <HelloDuan></HelloDuan>
// <hello-duan></hello-duan>
// </div>
// `
// });
var HelloVue = {
data: function() {
return {
msg: 'Hello Vue'
};
},
template: '<div>{{msg}}</div>'
};
var HelloDuan = {
data: function() {
return {
msg: 'Hello Duanxx'
};
},
template: '<div>{{msg}}</div>'
};
var HelloJS = {
data: function() {
return {
msg: 'Hello JS'
};
},
template: '<div>{{msg}}</div>'
};
var vm = new Vue({
el: '#app',
data: {},
components: {
'hello-vue': HelloVue,
'hello-duan': HelloDuan,
'hello-js': HelloJS,
}
})
</script>
</body>
</html>
三. Vue调试工具
3.1 调试工具安装
-
方法1
① 克隆仓库
② 安装依赖包
③ 构建
④ 打开Chrome扩展页面
⑤ 选中开发者模式
⑥ 加载已解压的扩展,选择shells/chrome
-
方法2
四. 组件间数据交互
4.1 父组件向子组件传值
1. 组件内部通过props接收传递过来的值
Vue.component('menu-item', {
props: ['title'],
template: '<div>{{ title }}</div>'
});
2. 父组件通过属性将值传递给子组件
<menu-item title="来自父组件的数据"></menu-item>
<menu-item :title="title"></menu-item>
案例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>父组件向子组件传值</title>
</head>
<body>
<div id="app">
<div>{{pmsg}}</div>
<menu-item title="来自父组件中的内容"></menu-item>
<menu-item :title="omsg" content="段小小"></menu-item>
</div>
<script src="./js/vue.js"></script>
<script type="text/javascript">
Vue.component('menu-item', {
props: ['title', 'content'],
data: function() {
return {
msg: '子组件本身数据',
}
},
template: `<div>{{msg + "----" + title + "----" + content}}</div>`,
})
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中的内容',
omsg: '动态绑定属性'
},
})
</script>
</body>
</html>
3.props属性名规则
- 在props中使用驼峰形式,模板中需要使用短横线的形式
- 字符串形式的模板中没有这个限制
Vue.component('menu-item', {
// 在 JavaScript 中是驼峰式的
props: [‘menuTitle'],
template: '<div>{{ menuTitle }}</div>'
});
<!– 在html中是短横线方式的 -->
<menu-item menu-title="nihao"></menu-item>
案例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>props命名规范</title>
</head>
<body>
<div id="app">
<div>{{pmsg}}</div>
<!-- 在props中使用驼峰形式,模板中需要使用短横线的形式 -->
<menu-item :menu-title='ptitle'></menu-item>
</div>
<script src="./js/vue.js"></script>
<script type="text/javascript">
// 三级组件
Vue.component('third-com', {
props: ['testTile'],
template: '<div>{{testTile}}</div>'
});
// 二级组件
Vue.component('menu-item', {
props: ['menuTitle'],
// 字符串形式的模板中没有这个限制
template: '<div>{{menuTitle}}<third-com testTile="Hello Vue"></third-com></div>'
})
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中的内容',
ptitle: '动态绑定属性'
},
})
</script>
</body>
</html>
4.props属性值类型
- 字符串 String
- 数值 Number
- 布尔值 Boolean
- 数组 Array
- 对象 Object
案例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>props属性值类型</title>
</head>
<body>
<div id="app">
<div>{{pmsg}}</div>
<!-- 对于 数值类型 和 布尔类型 通过 v-bind 绑定显示原类型,否则显示为 string -->
<menu-item :pstr="pstr" :pnum="12" :pboo="true" :parr="parr" :pobj="pobj"></menu-item>
</div>
<script src="./js/vue.js"></script>
<script type="text/javascript">
Vue.component('menu-item', {
props: ['pstr', 'pnum', 'pboo', 'parr', 'pobj'],
template: `
<div>
<div>{{pstr}}</div>
<div>{{12 + pnum}}</div>
<div>{{typeof pboo}}</div>
<ul>
<li :key='index' v-for='(item,index) in parr'>{{item}}</li>
</ul>
<div>
<span>{{pobj.name}}</span>
<span>{{pobj.age}}</span>
</div>
</div>
`,
})
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中的内容',
pstr: 'Hello Vue',
parr: ['apple', 'banana', 'orange'],
pobj: {
name: '段小小',
age: 18
}
},
})
</script>
</body>
</html>
4.2 子组件向父组件传值
1. 不带参数
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>子传父基本用法</title>
</head>
<body>
<div id="app">
<div :style='{fontSize:fontSize + "px"}'>{{pmsg}}</div>
<!-- 2. 父组件监听子组件的事件 -->
<menu-item :parr='parr' @enlarge-text='handle'></menu-item>
<!-- <menu-item v-on:enlarge-text=事件对应的处理逻辑'></menu-item> -->
</div>
<script src="./js/vue.js"></script>
<script type="text/javascript">
// props 传递数据原则:单向数据流(只允许父组件向子组件传递参数,不允许子组件操作 props)
// 如果允许子组件操作,数据的控制逻辑就比较复杂,不太容易控制。
// 从代码运行中可看出子组件可以操作 props 但是这种操作不推荐。
Vue.component('menu-item', {
props: ['parr'],
template: `
<div>
<ul>
<li :key='index' v-for='(item,index) in parr'>{{item}}</li>
</ul>
<!-- 不推荐 -->
<button @click='parr.push("lemon")'>点击</button>
<!-- 1. 子组件通过自定义事件向父组件传递信息 -->
<button @click='$emit("enlarge-text")'>扩大父组件中字体大小</button>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中内容',
parr: ['apple', 'orange', 'banana'],
fontSize: 10
},
methods: {
handle: function() {
// 扩大字体大小
this.fontSize += 5;
}
}
})
</script>
</body>
</html>
2.带参数
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>子传父基本用法</title>
</head>
<body>
<div id="app">
<div :style='{fontSize:fontSize + "px"}'>{{pmsg}}</div>
<!-- 4. 父组件监听子组件的事件 -->
<menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
</div>
<script src="./js/vue.js"></script>
<script type="text/javascript">
Vue.component('menu-item', {
props: ['parr'],
template: `
<div>
<!-- 3. 子组件通过自定义事件向父组件传递信息 -->
<button @click='$emit("enlarge-text",5)'>扩大父组件中字体大小</button>
<button @click='$emit("enlarge-text",10)'>扩大父组件中字体大小</button>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中内容',
fontSize: 10
},
methods: {
handle: function(val) {
// 扩大字体大小
this.fontSize += val;
}
}
})
</script>
</body>
</html>
4.3 非父子组件间传值
1. 单独的事件中心管理组件间的通信
var eventHub = new Vue();
2. 监听事件与销毁事件
eventHub.$on('add-todo', addTodo)
// 参数1:事件名称 参数2:事件函数
eventHub.$off('add-todo')
3. 触发事件
eventHub.$emit(‘add-todo', id)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>兄弟互传</title>
</head>
<body>
<div id="app">
<h2>父组件</h2>
<div>
<button @click='handle'>销毁事件</button>
</div>
<module-one></module-one>
<module-two></module-two>
</div>
<script src="./js/vue.js"></script>
<script type="text/javascript">
//提供事件中心
var hub = new Vue();
Vue.component('module-one', {
data: function() {
return {
num: 0
}
},
template: `
<div>
<div>One:{{num}}</div>
<div>
<button @click='handle'>点击</button>
</div>
</div>
`,
methods: {
handle: function() {
// 触发兄弟组件的事件
hub.$emit('two-event', 2);
},
},
mounted: function() {
// 监听事件
hub.$on('one-event', (val) => {
this.num += val;
});
}
});
Vue.component('module-two', {
data: function() {
return {
num: 0
}
},
template: `
<div>
<div>Two:{{num}}</div>
<div>
<button @click='handle'>点击</button>
</div>
</div>
`,
methods: {
handle: function() {
// 触发兄弟事件
hub.$emit('one-event', 1);
}
},
mounted: function() {
// 监听事件
hub.$on('two-event', (val) => {
this.num += val;
});
}
});
var vm = new Vue({
el: '#app',
data: {},
methods: {
handle: function() {
hub.$off('one-event');
hub.$off('two-event');
}
}
})
</script>
</body>
</html>
五. 组件插槽
5.1 组件插槽的作用
1.父组件向子组件传递内容
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>
案例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件插槽基本用法</title>
</head>
<body>
<div id="app">
<err-box>语法错误!</err-box>
<err-box>数据上传错误!</err-box>
<err-box></err-box>
</div>
<script src="./js/vue.js"></script>
<script type="text/javascript">
Vue.component('err-box', {
template: `
<div>
<strong>ERROR:</strong>
<slot>出现了错误!</slot>
</div>
`,
})
var vm = new Vue({
el: '#app',
data: {
},
methods: {
}
})
</script>
</body>
</html>
5.3 具名插槽用法
案例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>具名插槽</title>
</head>
<body>
<div id="app">
<span>---------------- 用法一 ------------------</span>
<!-- 2. 插槽内容 -->
<base-layout>
<p slot="header">标题信息</p>
<p>主要内容</p>
<p>段小小大帅逼!</p>
<p slot="footer">底部信息内容</p>
</base-layout>
<span>---------------- 用法二 ------------------</span>
<base-layout>
<!-- template 属于 vue API -->
<!-- 可以向插槽中放入多个标签 -->
<template slot="header">
<p>标题信息</p>
<p>my logo</p>
</template>
<p>主要内容</p>
<p>段仙子大帅逼!</p>
<template slot="footer">
<p>底部信息内容</p>
</template>
</base-layout>
</div>
<script src="./js/vue.js"></script>
<script type="text/javascript">
// 具名插槽
Vue.component('base-layout', {
template: `
<!-- 1. 插槽定义 -->
<div>
<header>
<slot name='header'></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name='footer'></slot>
</footer>
</div>
`,
});
var vm = new Vue({
el: '#app',
data: {},
})
</script>
</body>
</html>
5.4 作用域插槽
应用场景:
父组件对子组件的内容进行加工处理
案例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>作用域插槽</title>
<style>
.current {
color: orange;
}
</style>
</head>
<body>
<div id="app">
<fruit-list :list='list'>
<template slot-scope='slotProps'>
<strong v-if='slotProps.info.id == 2' class="current">{{slotProps.info.name}}</strong>
<span v-else>{{slotProps.info.name}}</span>
</template>
</fruit-list>
</div>
<script src="./js/vue.js"></script>
<script type="text/javascript">
Vue.component('fruit-list', {
props: ['list'],
template: `
<div>
<li :key='item.id' v-for='item in list'>
<slot :info='item'>{{item.name}}</slot>
</li>
</div>
`,
})
var vm = new Vue({
el: '#app',
data: {
list: [{
id: 1,
name: 'apple',
}, {
id: 2,
name: 'lemon',
}, {
id: 3,
name: 'orange',
}]
},
})
</script>
</body>
</html>