组件
是可复用的 Vue 实例, 把一些公共的模块抽取出来,然后写成单独的的工具组件或者页面,在需要的页面中就直接引入即可,那么我们可以将其抽出为一个组件进行复用,提高了代码的复用率
组件化开发思想
:如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。但如果,我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
组件化思想的应用
:
使用Vue.component('button-counter', cpnc)
方式注册,直接使用
调用。button-counter
是全局组件的名字,cpnc
是定义的组件对象。
Vue.component("button-counter", {
data: 组件数据,
template: 组件模块内容
});
//注册一个名为button-counter 的新组件
Vue.component("button-counter", {
data: function () {
return {
count: 0,
};
},
template: '',
});
<div id="app">
<button-counter>button-counter>
<button-counter>button-counter>
div>
//1-组件的基本使用.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title></title>
</head>
<body>
<div id="app">
<!-- 全局组件的使用 -->
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
//全局组件的注册
Vue.component("button-counter", {
data: function () {
return {
count: 0,
};
},
template: `
`,
});
const app = new Vue({
el: "#app",
data: {},
methods: {},
});
</script>
</body>
</html>
data必须是一个函数
保证每个模板的数据是相互独立的
组件模板内容必须是单个跟元素
只能有一个父元素,如上面代码中的div
组件模板内容可以是模板字符串``
ES6 引入新的声明字符串的方式 ``
组件命名方式
短横线方式 my-component (推荐)
驼峰方式 MyComponent
局部组件
,只能在当前vue实例挂载的对象中使用,类似于局部变量,有块级作用域。
//注册方式
const app = new Vue({
el:"#app",
components:{//局部组件创建
'hello-world': HelloWorld
}
})
使用方式与全局变量一样,直接使用
调用。 'hello-world’组件命名的名字,HelloWorld定义的组件对象。
//04-局部组件用法.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<hello-world></hello-world>
</div>
<script type='text/javascript' src='js/vue.js'></script>
<script type='text/javascript'>
var HelloWorld = {
data: function () {
return {
msg: 'helloworld'
}
},
template: `
{{msg}}
`
}
const app = new Vue({
el: "#app",
data: {},
components: {
'hello-world': HelloWorld
}
})
</script>
</body>
</html>
1.子组件内部通过props接收父组件传递过来的值
props传递数据原则:单向数据流,只能父传子
v-bind是不支持使用驼峰标识的,例如cUser
要改成c-User
。
Vue.component(‘menu-item',{
//props接受父组件传递的数据
props: ['title'],
template: '{{ title }}'
})
2.父组件通过属性将值传递给子组件
//传统方式
<menu-item title="来自父组件的数据"></menu-item>
//动态绑定
<menu-item :title="title"></menu-item>
//07-父组件向子组件传值-props命名规则.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<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 type='text/javascript' src='js/vue.js'></script>
<script type='text/javascript'>
//相对于Vue实例为子组件
Vue.component('menu-item', {
//props接受父组件传递的数据
props: ['title', 'content'],
data: function () {
return {
msg: '子组件本身的数据'
}
},
template: `
{{msg + '-----' +title + '-----' +content}}
`
})
//Vue实例为根组件
const app = new Vue({
el: "#app",
data: {
pmsg: "父组件中内容",
ptitle: '动态绑定的值'
}
})
</script>
</body>
</html>
3.props属性名规则
在props中使用驼峰形式,在html中需要使用短横线的形式
字符串形式的模板中没有这个限制
Vue.component(‘menu-item', {
// 在JavaScript中是驼峰式的
props: ['menuTitle'],
template: '{{ menuTitle }}'
})
//在html中是短横线方式的
<menu-item menu-title=“nihao"></menu-item>
4.props属性值类型
字符串String
数值Number
布尔值Boolean
数组Array
对象Object
Document
{{pmsg}}
//字符串 数字 布尔类型 数组 对象
1.子组件通过自定义事件向父组件传递信息
子组件向父组件传值,使用自定义事件$emit
。$emit(自定义事件名称)
<button @click='$emit("enlarge-text")'>扩大父组件字体</button>
2.在父组件中监听子组件的事件
<menu-item @enlarge-text='handle'></menu-item>
//09-子组件向父组件传值.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<!-- 整个app为父组件 -->
<div id="app">
<div :style='{fontSize:fontsize+"px"}'>{{msg}}</div>
<!-- 在父组件中监听子组件事件 -->
<menu-item @enlarge-text='handle'></menu-item>
</div>
<script type='text/javascript' src='js/vue.js'></script>
<script type='text/javascript'>
//子组件点击按钮控制父组件字体大小
//创建子组件
Vue.component('menu-item', {
template: `
`
})
const app = new Vue({
el: "#app",
data: {
msg: '父组件中的内容',
fontsize: 10
},
methods: {
handle: function () {
this.fontsize += 5;
}
}
})
</script>
</body>
</html>
3.子组件通过自定义事件向父组件传递信息,携带额外的值
5为另外额外的值
<button @click='$emit("enlarge-text", 5)'>扩大父组件中字体大小</button>
4.父组件监听子组件的事件
通过$event接受额外的值
<menu-item @enlarge-text='handle($event)'></menu-item>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<div :style='{fontSize:fontsize+"px"}'>{{msg}}</div>
<menu-item @enlarge-text='handle($event)'></menu-item>
</div>
<script type='text/javascript' src='js/vue.js'></script>
<script type='text/javascript'>
Vue.component('menu-item', {
template: `
`
})
const app = new Vue({
el: "#app",
data: {
msg: '父组件中的内容',
fontsize: 10
},
methods: {
handle: function (val) {
this.fontsize += val;
}
}
})
</script>
</body>
</html>
1.单独的事件中心管理组件间的通信
var eventHub = new Vue()
2.监听事件与销毁事件
$on用于监听事件,add-todo为事件名称,addTodo为事件函数
$off用于销毁事件
eventHub.$on('add-todo', addTodo)
eventHub.$off('add-todo')
3.触发事件
$emit用于触发事件,add-todo为事件名称, id为携带参数
eventHub.$emit(‘add-todo', id)
//11-兄弟组件之间数据交互.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<div>父组件</div>
<div>
<button @click='handle'>销毁事件</button>
</div>
<test-tom></test-tom>
<test-jerry></test-jerry>
</div>
<script type='text/javascript' src='js/vue.js'></script>
<script type='text/javascript'>
//提供事件中心
var hub = new Vue()
Vue.component('test-tom', {
data: function () {
return {
num: 0
}
},
template: `
Tom:{{num}}
`,
methods: {
handle: function () {
//触发兄弟组件的事件
hub.$emit('jerry-event', 2)
}
},
mounted: function () {
//监听事件
hub.$on('tom-event', val => {
this.num += val;
})
}
})
Vue.component('test-jerry', {
data: function () {
return {
num: 0
}
},
template: `
Jerry:{{num}}
`,
methods: {
handle: function () {
//触发兄弟组件的事件
hub.$emit('tom-event', 1)
}
},
mounted: function () {
//监听事件
hub.$on('jerry-event', val => {
this.num += val;
})
}
})
const app = new Vue({
el: "#app",
data: {},
methods: {
handle: function () {
//销毁事件
hub.$off('tom-event');
hub.$off('jerry-event')
}
}
})
</script>
</body>
</html>
编译作用域
:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
插槽的目的是让我们原来的设备具备更多的扩展性。比如电脑的USB我们可以插入U盘、硬盘、手机、音响、键盘、鼠标等等。
组件的插槽
是为了让我们封装的组件更加具有扩展性,让使用者可以决定组件内部的一些内容到底展示什么。
组件插槽的作用
:父组件向子组件传递内容
在子组件中,使用特殊的元素
就可以为子组件开启一个插槽。
该插槽插入什么内容取决于父组件如何使用。
如何封装合适呢?
抽取共性,保留不同。最好的封装方式就是将共性抽取到组件中,将不同预留为插槽。
1.插槽位置
Vue.component('alert-box', {
template: `
Error!
`
})
2.插槽内容
Something had happened.
//12-插槽基本用法.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<alert-box>有bug发生</alert-box>
<alert-box>有一个警告</alert-box>
<alert-box></alert-box>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
Vue.component('alert-box', {
template: `
ERROR:
默认内容
`
});
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>
1. 插槽定义
2.插槽内容
//根据名称进行匹配,没有匹配到的放在默认插槽中
<base-layout>
<h1 slot="header">标题内容</h1>
<p>主要内容1</p>
<p>主要内容2</p>
<p slot="footer">底部内容</p>
</base-layout>
//13-具名插槽用法.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<base-layout>
<template slot='header'>
<p>标题信息1</p>
<p>标题信息2</p>
</template>
<p>主要内容1</p>
<p>主要内容2</p>
<template slot='footer'>
<p>底部信息信息1</p>
<p>底部信息信息2</p>
</template>
</base-layout>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
Vue.component('base-layout', {
template: `
`
});
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>
应用场景
:父组件对子组件的内容进行加工处理
1.插槽定义
<ul>
<li v-for= "item in list" v-bind:key= "item.id" >
<slot v-bind:item="item">
{{item.name}}
</slot>
</li>
</ul>
2.插槽内容
<fruit-list v-bind:list= "list">
<template slot-scope="slotProps">
<strong v-if="slotProps.item.current">
{{ slotProps.item.text }}
</strong>
</template>
</fruit-list>
//14-作用域插槽用法.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<style type="text/css">
.current {
color: orange;
}
</style>
<body>
<div id="app">
<fruit-list :list='list'>
<template slot-scope='slotProps'>
<strong v-if='slotProps.info.id==3' class="current">{{slotProps.info.name}}</strong>
<span v-else>{{slotProps.info.name}}</span>
</template>
</fruit-list>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
Vue.component('fruit-list', {
props: ['list'],
template: `
{{item.name}}
`
});
var vm = new Vue({
el: '#app',
data: {
list: [{
id: 1,
name: 'apple'
},{
id: 2,
name: 'orange'
},{
id: 3,
name: 'banana'
}]
}
});
</script>
</body>
</html>
案例需求分析
:按照组件化方式实现业务需求
根据业务功能进行组件化划分
①标题组件(展示文本)
②列表组件(列表展示、商品数量变更、商品删除)
③结算组件(计算商品总额)
功能实现步骤
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 引入css样式 -->
<link rel="stylesheet" type="text/css" href="购物车案例.css" />
</head>
<body>
<div id="app">
<div class="container">
<my-cart></my-cart>
</div>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
//标题组件
var CartTitle = {
//传递用户名数据
props: ['uname'],
template: `
{{uname}}的商品
`
}
//列表组件
var CartList = {
props: ['list'],
//列表展示
//列表名
//商品数量变更
//商品数量减
//呈现数据 失去焦点呈现数据
//商品数量加
//商品删除
template: `
`,
methods: {
//商品数量输入域修改
changeNum: function (id, event) {
this.$emit('change-num', {
id: id,
type: 'change',
num: event.target.value //当前输入域最新值
});
},
//商品数量减
add: function (id) {
this.$emit('change-num', {
id: id,
type: 'add'
});
},
//商品数量加
sub: function (id) {
this.$emit('change-num', {
id: id,
type: 'sub'
});
},
//商品删除
del: function (id) {
// 把id传递给父组件
this.$emit('cart-del', id);
}
}
}
//结算组件
var CartTotal = {
props: ['list'],
template: `
总价:{{total}}
`,
computed: {
total: function () {
// 计算商品的总价
var t = 0;
this.list.forEach(item => {
t += item.price * item.num;
});
return t;
}
}
}
Vue.component('my-cart', {
data: function () {
return {
uname: '张三',
list: [{
id: 1,
name: 'TCL彩电',
price: 1000,
num: 1,
img: 'img/a.jpg'
}, {
id: 2,
name: '机顶盒',
price: 1000,
num: 1,
img: 'img/b.jpg'
}, {
id: 3,
name: '海尔冰箱',
price: 1000,
num: 1,
img: 'img/c.jpg'
}, {
id: 4,
name: '小米手机',
price: 1000,
num: 1,
img: 'img/d.jpg'
}, {
id: 5,
name: 'PPTV电视',
price: 1000,
num: 1,
img: 'img/e.jpg'
}]
}
},
template: `
`,
components: {
'cart-title': CartTitle,
'cart-list': CartList,
'cart-total': CartTotal
},
methods: {
changeNum: function (val) {
// 商品数量变更
//分为三种情况:商品数量输入域变更、商品数量加、商品数量减
if (val.type == 'change') {
// 根据子组件传递过来的数据,跟新list中对应的数据
this.list.some(item => {
if (item.id == val.id) {
item.num = val.num;
// 终止遍历
return true;
}
});
} else if (val.type == 'sub') {
// 减一操作
this.list.some(item => {
if (item.id == val.id) {
item.num -= 1;
// 终止遍历
return true;
}
});
} else if (val.type == 'add') {
// 加一操作
this.list.some(item => {
if (item.id == val.id) {
item.num += 1;
// 终止遍历
return true;
}
});
}
},
//列表删除功能
delCart: function (id) {
// 根据id删除list中对应的数据
// 1、找到id所对应数据的索引
var index = this.list.findIndex(item => {
return item.id == id;
});
// 2、根据索引删除对应数据
this.list.splice(index, 1);
}
}
});
var vm = new Vue({
el: '#app',
});
</script>
</body>
</html>