源码示例链接:https://pan.baidu.com/s/1NEYDmLl2K7nNa-AKWtJqVA
提取码:2c7a
目标
- 能够知道组件化开发思想
- 能够知道组件的注册方式
- 能够说出组件间的数据交互方式能够说出组件插槽的用法
- 能够说出Vue调试工具的用法
- 能够基于组件的方式实现业务功能
组件化开发思想
现实中的组件化思想体现
-
标准
-
分治
-
重用
-
组合
编程中的组件化思想体现
组件化规范: Web Components(有的浏览器不支持)
我们希望尽可能多的重用代码
自定义组件的方式不太容易(html、css和js)多次使用组件可能导致冲突
Web Components通过创建封装好功能的定制元素解决上述问题
官网:
https://developer.mozilla.org/zh-CN/docs/Web/Web_Components
Vue部分实现了上述规范
组件注册
全局组件注册语法
Vue.component(组件名称,
data:组件数据,
template:组件模板内容
})
//定义一个名为button-counter的新组件
Vue.component('button-counter',{
data: function () {
return {
count: 0
}
},
template: '<button @click="count++">点击了{{count}}次</button>'
})
组件用法
<div id="app">
<button-counter></button-counter>>
</div>
实例1:
<!DOCTYPE html>
<html lang="en">
<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>
</div>
<script src="js/vue.js"></script>
<script>
/*
注册组件
*/
Vue.component('button-counter',{
data: function () {
return {
count: 0
}
},
template: '<button @click="count++">点击了{{count}}次</button>'
})
var vm = new Vue({
el: "#app",
data: {
}
})
</script>
</body>
</html>
实例2:
<!DOCTYPE html>
<html lang="en">
<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>
<button-counter></button-counter>
</div>
<script src="js/vue.js"></script>
<script>
/*
注册组件
*/
// 定义一个名为button-counter的新组件
Vue.component('button-counter',{
data: function () {
return {
count: 0
}
},
template: '<button @click="handle">点击了{{count}}次</button>',
methods: {
handle: function () {
this.count += 2;
}
}
})
var vm = new Vue({
el: "#app",
data: {
}
})
</script>
</body>
</html>
组件注册的书注意事项
-
data必须是一个函数
一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝
-
组件模板内容必须是单个根元素(2个或2个以上元素必须被一个根元素嵌套起来)
template中的内容必须被一个DOM元素包裹 ,也可以嵌套(每个组件必须只有一个根元素)
错误示例:
template: '<button @click="handle">点击了{{count}}次</button><button>测试</button>', // 报错:Component template should contain exactly one root element
正确实例:
template: '<div><button @click="handle">点击了{{count}}次</button><button>测试</button></div>'
-
组件模板内容可以是模板字符串(反引号)
模板字符串需要浏览器提供支持(ES6语法)
-
组件模板内容template如果用引号把内容括起来的话,多个标签是不能换行的。
-
使用模板字符串(反引号)把template的内容括起来,多个标签是可以换行的。
-
-
组件命名方式**(推荐使用短横线方式)**
-
短横线方式
vue.component ('my-component',{ /* ... */ })
-
驼峰方式
vue.component ('Mycomponent',{ /* ... */ })
如果使用驼峰式命名组件,那么在使用组件的时候,只能在字符串模板中用驼峰的方式使用组件,但是在普通的标签模板中,必须使用短横线的方式使用组件
-
局部组件注册
var HelloWorld = {
data: function () {
return{
msg : 'HelloWorld'
}
},
template: '<div>{{msg}}</div>'
};
var HelloTom = {
data: function () {
return{
msg : 'HelloTom'
}
},
template: '<div>{{msg}}</div>'
};
var HelloJenny = {
data: function () {
return{
msg : 'HelloJenny'
}
},
template: '<div>{{msg}}</div>'
}
var vm = new Vue({
el: "#app",
data: {
},
// 注册局部组件
components: {
// 局部组件名称 : 组件内容
'hello-world': HelloWorld,
'hello-tom': HelloTom,
'hello-jenny': HelloJenny
}
})
调用:
<div id="app">
<hello-world></hello-world>
<hello-tom></hello-tom>
<hello-jenny></hello-jenny>
</div>
实例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>注册局部组件</title>
</head>
<body>
<div id="app">
<hello-world></hello-world>
<hello-tom></hello-tom>
<hello-jenny></hello-jenny>
</div>
<script src="js/vue.js"></script>
<script>
/*
注册局部组件
局部组件只能在注册它的父组件中使用
*/
// 在全局组件中使用局部组件
/* Vue.component('test-com',{
template: '<div>Test<hello-world></hello-world></div>'
}) */
// 编写组件的内容
var HelloWorld = {
data: function () {
return{
msg : 'HelloWorld'
}
},
template: '<div>{{msg}}</div>'
};
var HelloTom = {
data: function () {
return{
msg : 'HelloTom'
}
},
template: '<div>{{msg}}</div>'
};
var HelloJenny = {
data: function () {
return{
msg : 'HelloJenny'
}
},
template: '<div>{{msg}}</div>'
}
var vm = new Vue({
el: "#app",
data: {
},
// 注册局部组件
components: {
// 局部组件名称 : 组件内容
'hello-world': HelloWorld,
'hello-tom': HelloTom,
'hello-jenny': HelloJenny
}
})
</script>
</body>
</html>
Vue调试工具用法
谷歌插件:
https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd
实例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue调试工具</title>
<style>
.root {
background-color: orange;
}
.second {
background-color: palegreen;
}
.third {
background-color: royalblue;
}
</style>
</head>
<body>
<div id="app" class="root">
<div>{{root}}</div>
<second-com></second-com>
<second-com></second-com>
</div>
<script src="js/vue.js"></script>
<script>
/*
Vue调试工具安装与基本使用
*/
Vue.component('second-com',{
data: function () {
return {
second: '二级插件'
}
},
template: `<div class="second">
<div>{{second}}</div>
<third-com></third-com>
<third-com></third-com>
<third-com></third-com>
</div>`
});
Vue.component('third-com',{
data: function () {
return {
third: '三级组件'
}
},
template: '<div class="third"><div>{{"third"}}</div></div>'
});
var vm = new Vue({
el: "#app",
data: {
root: '顶层组件'
}
})
</script>
</body>
</html>
组件间数据交互
组件内部通过props接收传递过来的值
子组件的props: 接收父组件传递过来的数据
Vue.component('menu-item',{
props: ['title'],
template: '<div>{{ title }}</div>'
})
父组件通过属性将值传递给子组件
<menu-item title="来自父组件的数据"></menu-item>
<menu-item :title="title"></menu-item>
实例:
<!DOCTYPE html>
<html lang="en">
<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="ptitle" content='hello'></menu-item>
</div>
<script src="js/vue.js"></script>
<script>
/*
父组件向子组件传值
*/
Vue.component('menu-item',{
// 子组件的props: 接收父组件传递过来的数据
props: ['title','content'],
data: function () {
return {
msg: "子组件本身的数据"
}
},
template: '<div>{{msg + "----" + title + "----" + content}}</div>'
})
var vm = new Vue({
el: "#app",
data: {
pmsg: "父组件中的内容",
ptitle: "动态绑定属性"
}
})
</script>
</body>
</html>
props属性名规则
- 在props中使用驼峰形式,模板中需要使用驼峰的形式
- 字符串形式的模板中没有这个限制
对于props的属性,
在父组件声明的template模板中,属性名需要使用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="en">
<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>
<!-- 父组件的值传给子组件:第二种方式 可以传多个值 -->
<menu-item :menu-title="ptitle"></menu-item>
</div>
<script src="js/vue.js"></script>
<script>
/*
父组件向子组件传值
*/
// 字符串形式的模板template中可以使用 短横线 或 驼峰式
// 驼峰式
Vue.component('third-com',{
// 子组件的props: 接收父组件传递过来的数据
props: ['testTitle'],
template: '<div>{{testTitle}}</div>'
})
// 短横线
Vue.component('menu-item',{
// 子组件的props: 接收父组件传递过来的数据
props: ['menuTitle'],
template: '<div>{{menuTitle}}<third-com testTitle="在符串形式的模板template中可使用驼峰式"></third-com></div>'
})
var vm = new Vue({
el: "#app",
data: {
pmsg: "父组件中的内容",
ptitle: "动态绑定属性"
}
})
</script>
</body>
</html>
props属性值类型
- 字符串 String
- 数值 Number
- 布尔值 Boolean
- 数组 Array
- 对象 Object
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>props属性值类型</title>
</head>
<body>
<div id="app">
<!-- 使用全局组件 menu-item -->
<!-- 数值类型 和 布尔类型 ,
如果通过 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>
// 注册全局组件
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: "lisi",
age: "18"
}
}
})
</script>
</body>
</html>
子组件通过自定义事件向父组件传递信息
不带参数
<!-- $emit
名字是固定的,不能自定义
作用:用户触发自定义事件 -->
<button v-on:click='$emit ("enlarge-text")'>扩大字体</button>
带参数
<button @click='$emit("enlarge-text",5)'>扩大父组件中字体大小</button>
父组件监听组件的事件
不带参数
<div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
带参数
<menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
实例1(不带参数):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>子组件向父组件传值</title>
</head>
<body>
<div id="app">
<!-- :style='{fontSize: fontSize + "px"} 给内容家加样式 -->
<div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
<!-- 父组件监听子组件的事件 -->
<menu-item :parr='parr' @enlarge-text='handle'></menu-item>
</div>
<script src="js/vue.js"></script>
<script>
// props 传递数据原则:单向数据流(只允许父组件向子组件传递参数,不允许子组件操作 props)
// 如果允许子组件操作,数据的控制逻辑就比较复杂,不太容易控制。
// 注册组件
Vue.component("menu-item",{
props: ['parr'],
template: `
<div>
<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="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>子组件向父组件传值</title>
</head>
<body>
<div id="app">
<!-- :style='{fontSize: fontSize + "px"} 给内容家加样式 -->
<div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
<!-- 父组件监听子组件的事件 -->
<menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
</div>
<script src="js/vue.js"></script>
<script>
// props 传递数据原则:单向数据流(只允许父组件向子组件传递参数,不允许子组件操作 props)
// 如果允许子组件操作,数据的控制逻辑就比较复杂,不太容易控制。
// 注册组件
Vue.component("menu-item",{
props: ['parr'],
// $emit("要触发的事件", 触发事件的参数) -- 参数要根据情况,写还是不写
// 作用:用户触发自定义事件
template: `
<div>
<button @click='$emit("enlarge-text",5)'>扩大父组件中字体大小</button>
</div>
`
})
var vm = new Vue({
el: "#app",
data: {
pmsg: "父组件中的内容",
// parr: ['apple','orange','banana'],
fontSize: 10
},
methods: {
handle: function(val) {
// 扩大字体大小
this.fontSize += val;
}
}
})
</script>
</body>
</html>
非父子组件间传值
单独的事件中心管理组件间的通信
事件中心:
var eventHub = new Vue()
监听事件与销毁事件
eventHub.$on ("add-todo',addTodo)
/*
参数1:事件名称
参数2:事件函数
*/
eventHub.$off ('add-todo')
触发事件
eventHub.$emit ('add-todo',id)
实例:
<!DOCTYPE html>
<html lang="en">
<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>
// 提供事件中心
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('one-event',1)
}
},
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('two-event',2)
}
},
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>
父子组件之间可以直接传值;
非父子组件之间通过事件中心传值
组件插槽
父组件向子组件传递内容
插槽slot名字是固定的
插槽里面是可变的内容,也可以在slot标签里面写上默认的内容。
插槽位置
Vue.component('err-box',{
template: `
<div>
<strong>ERROR:</strong>
<slot>出错了</slot>
</div>`
})
插槽内容
<!-- 插槽里的默认内容 -->
<err-box></err-box>
<!-- 插槽里变化的内容 -->
<err-box>语法错误</err-box>
<err-box>拼写错误</err-box>
实例:
<!DOCTYPE html>
<html lang="en">
<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>
Vue.component('err-box',{
template: `
<div>
<strong>ERROR:</strong>
<slot>出错了</slot>
</div>`
})
var vm = new Vue({
el: '#app',
data: {
},
methods: {
}
})
</script>
</body>
</html>
具名插槽用法
每个插槽通过添加name属性有列自己的名字 <slot name="插槽的名字"></slot>
定义插槽
Vue.component('base-layout',{
template: `
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>`
});
插槽内容
<span>---------------------用法一(每一个插槽只能填充一条数据)------------------------</span>
<base-layout>
<!-- 把内容插入到名字为header的插槽中 -->
<p slot="header">标题信息</p>
<!-- 把内容插入到没有名字的插槽中 -->
<p>主要内容</p>
<!-- 把内容插入到名字为header的插槽中 -->
<p slot="footer">底部信息</p>
</base-layout>
<span>---------------------用法二(每一个插槽可以填充多条数据,使用template标签)------------------------</span>
<base-layout>
<!-- template 属于 vue API -->
<!-- 使用template标签可以向每个插槽填充多条数据 -->
<!-- 把内容插入到名字为header的插槽中 -->
<template slot="header">
<p>标题信息1</p>
<p>标题信息2</p>
</template>
<!-- 把内容插入到没有名字的插槽中 -->
<template>
<p>主题内容1</p>
<p>主题内容2</p>
</template>
<!-- 把内容插入到名字为header的插槽中 -->
<template slot="footer">
<p>底部信息1</p>
<p>底部信息2</p>
</template>
</base-layout>
实例:
<!DOCTYPE html>
<html lang="en">
<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>
<base-layout>
<!-- 把内容插入到名字为header的插槽中 -->
<p slot="header">标题信息</p>
<!-- 把内容插入到没有名字的插槽中 -->
<p>主要内容</p>
<!-- 把内容插入到名字为header的插槽中 -->
<p slot="footer">底部信息</p>
</base-layout>
<span>---------------------用法二(每一个插槽可以填充多条数据)------------------------</span>
<base-layout>
<!-- template 属于 vue API -->
<!-- 使用template标签可以向每个插槽填充多条数据 -->
<!-- 把内容插入到名字为header的插槽中 -->
<template slot="header">
<p>标题信息1</p>
<p>标题信息2</p>
</template>
<!-- 把内容插入到没有名字的插槽中 -->
<template>
<p>主题内容1</p>
<p>主题内容2</p>
</template>
<!-- 把内容插入到名字为header的插槽中 -->
<template slot="footer">
<p>底部信息1</p>
<p>底部信息2</p>
</template>
</base-layout>
</div>
<script src="js/vue.js"></script>
<script>
// 具名插槽
Vue.component('base-layout',{
template: `
<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: {
},
methods: {
}
})
</script>
</body>
</html>
作用域插槽
应用场景:父组件对子组件的内容进行加工处理
定义作用域插槽
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>
`
});
使用作用域插槽
<fruit-list :list='list'>
<!-- slot-scope:通过插槽传递过来的数据。父组件就可以获得子组件的数据了。 -->
<template slot-scope='slotProps'>
<!-- 把id值为3的元素加粗,并把背景色设置为橘色 -->
<strong v-if='slotProps.info.id == 3' class="current">{{slotProps.info.name}}</strong>
</template>
</fruit-list>
slot-scope:通过插槽传递过来的数据。父组件就可以获得子组件的数据了。
实例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>作用域插槽</title>
<style>
.current {
background-color: orange;
}
</style>
</head>
<body>
<div id="app">
<fruit-list :list='list'>
<!-- slot-scope:通过插槽传递过来的数据。父组件就可以获得子组件的数据了。 -->
<template slot-scope='slotProps'>
<!-- 把id值为3的元素加粗,并把背景色设置为橘色 -->
<strong v-if='slotProps.info.id == 3' class="current">{{slotProps.info.name}}</strong>
</template>
</fruit-list>
</div>
<script src="js/vue.js"></script>
<script>
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>
如果这篇文章对你有帮助,别忘了点赞收藏关注哦~