Vue组件开发
一、组件化开发思想
1. 现实中的组件化思想体现
2. 编程中的组件化思想体现
3. 组件化规范:Web Components
-
希望尽可能多的重用代码
-
自定义组件的方式不太容易(html、css和js)
-
多次使用组件可能导致冲突
Web Componets通过创建封装好功能的定制元素解决上述问题
官网: https://developer.mozilla.org/zh-CN/docs/Web/Web_Components
二、组件注册
1. 全局组件注册语法
全局组件注册语法js模板代码如下:
Vue.component(组件名称, {
data: 组件数据,
template: 组件模板
})
js代码如下:
// 定义一个名为button-counter的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">点击{{count}}次</button>'
})
const vm = new Vue({
el: '#app',
data: {
name: ''
}
})
2. 组件用法
<div id="app">
<button-counter></button-counter>
</div>
3. 组件重用
js代码如下:
// 定义一个名为button-counter的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="handle">点击{{count}}次</button>',
methods: {
handle: function () {
this.count++;
}
}
})
const vm = new Vue({
el: '#app',
data: {
name: ''
}
})
html代码如下:
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
三、组件注册注意事项
1. data必须是一个函数
函数中返回一个对象,对象中存储的是每个组件独有的数据属性
分析函数与普通对象的对比,如果data中是一个对象的话会报如下的错误
html代码如下:
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
js代码如下:
// 定义一个名为button-counter的新组件
Vue.component('button-counter', {
data: {
count: 0
},
template: '<button v-on:click="handle">点击{{count}}次</button>',
methods: {
handle: function () {
this.count++;
}
}
})
const vm = new Vue({
el: '#app',
data: {
name: ''
}
})
2. 组件模板内容必须只有单个根元素
分析演示实际的效果如下:
html代码如下:
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
js代码如下:
// 定义一个名为button-counter的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="handle">点击{{count}}次</button><h1>测试</h1>',
methods: {
handle: function () {
this.count++;
}
}
})
const vm = new Vue({
el: '#app',
data: {
name: ''
}
})
3. 组件模板内容可以是模板字符串
模板字符串需要浏览器提供支持(ES6语法)
html代码如下:
<div id="app">
<button-counter></button-counter>
</div>
js代码如下:
// 定义一个名为button-counter的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: `
<div>
<button @click='handle'>点击{{count}}次</button>
<button @click='handle'>点击{{count}}次</button>
</div>
`,
methods: {
handle: function() {
this.count++;
}
}
})
const vm = new Vue({
el: '#app',
data: {
name: ''
}
})
4. 组件命名方式
-
短横线方式
Vue.component('my-component', { /* ... */ })
-
驼峰方式
Vue.component('myComponent', { /* ... */ })
html代码如下:
<div id="app">
<button-counter></button-counter>
</div>
js代码如下:
Vue.component('MyComponent', {
data: function () {
return {
msg: 'Hello Vue'
}
},
template: '<div>{{msg}}</div>'
})
// 定义一个名为button-counter的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: `
<div>
<button @click='handle'>点击{{count}}次</button>
<MyComponent></MyComponent>
</div>
`,
methods: {
handle: function () {
this.count++;
}
}
})
const vm = new Vue({
el: '#app',
data: {
name: ''
}
})
注意事项:
如果组件命名使用的是驼峰命名法,在网页组件标签中是不能使用驼峰命名法,只能使用短横线,但是在字符串模板中是可以使用驼峰命名法,建议使用短横线命名
js代码如下:
Vue.component('MyComponent', {
data: function () {
return {
msg: 'Hello Vue'
}
},
template: '<div>{{msg}}</div>'
})
// 定义一个名为button-counter的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: `
<div>
<button @click='handle'>点击{{count}}次</button>
<MyComponent></MyComponent>
</div>
`,
methods: {
handle: function () {
this.count++;
}
}
})
const vm = new Vue({
el: '#app',
data: {
name: ''
}
})
错误使用的html代码如下:
<div id="app">
<button-counter></button-counter>
<MyComponent></MyComponent>
</div>
会报如下错误:
正确使用的html代码如下:
<div id="app">
<button-counter></button-counter>
<My-Component></My-Component>
</div>
5. 局部组件注册
模板js代码如下:
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
// 然后在 components 选项中定义你想要使用的组件:
const vm = new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB,
'component-c': ComponentC
}
})
html代码如下:
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>
js代码如下:
var ComponentA = {
data: function () {
return {
msg: 'Hello ComponentA'
}
},
template: '<div>{{msg}}</div>'
};
var ComponentB = {
data: function () {
return {
msg: 'Hello ComponentB'
}
},
template: '<div>{{msg}}</div>'
};
var ComponentC = {
data: function () {
return {
msg: 'Hello ComponentC'
}
},
template: '<div>{{msg}}</div>'
};
const vm = new Vue({
el: '#app',
data: {
name: ''
},
components: {
'component-a': ComponentA,
'component-b': ComponentB,
'component-c': ComponentC
}
})
注意事项:
局部组件只能在注册它的父组件中使用
html代码如下:
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
<test-com></test-com>
</div>
js代码如下:
var ComponentA = {
data: function () {
return {
msg: 'Hello ComponentA'
}
},
template: '<div>{{msg}}</div>'
};
var ComponentB = {
data: function () {
return {
msg: 'Hello ComponentB'
}
},
template: '<div>{{msg}}</div>'
};
var ComponentC = {
data: function () {
return {
msg: 'Hello ComponentC'
}
},
template: '<div>{{msg}}</div>'
};
Vue.component('test-com', {
data: function () {
return {
msg: '测试'
}
},
template: `
<div>
{{msg}}
<component-a></component-a>
</div>
`
})
const vm = new Vue({
el: '#app',
data: {
name: ''
},
components: {
'component-a': ComponentA,
'component-b': ComponentB,
'component-c': ComponentC
}
})
会报如下错误:
四、组件间数据交互
1. 父组件向子组件传值
组件内部通过props接收父组件传递过来的值
Vue.component('menu-item', {
props: ['title'],
template: '<div>{{title}}</div>'
})
父组件通过属性将值传递给子组件
html代码如下:
<div id="app">
<menu-item title="hello"></menu-item>
<menu-item :title="title"></menu-item>
</div>
js代码如下:
Vue.component('menu-item', {
props: ['title'],
template: '<div>{{title}}</div>'
})
const vm = new Vue({
el: '#app',
data: {
title: 'form father'
}
})
props属性名规则
在props中使用驼峰形式,模板中需要使用短横线的形式
js代码如下:
Vue.component('menu-item', {
props: ['menuTitle'],
template: '<div>{{menuTitle}}</div>'
})
const vm = new Vue({
el: '#app',
data: {
title: 'form father'
}
})
html代码如下:
<div id="app">
<menu-item menuTitle="hello"></menu-item>
<menu-item :title="title"></menu-item>
</div>
会报如下警告:
将html代码改成如下就不会有了:
<div id="app">
<menu-item menu-Title="hello"></menu-item>
<menu-item :title="title"></menu-item>
</div>
字符串形式的模板中没有这个限制
html代码如下:
<div id="app">
<menu-item menu-Title="hello"></menu-item>
<menu-item :title="title"></menu-item>
</div>
js代码如下:
Vue.component('third-com', {
props: ['testTitle'],
template: '<div>{{testTitle}}</div>'
})
Vue.component('menu-item', {
props: ['menuTitle'],
template: `
<div>
{{menuTitle}}
<third-com testTitle="test"></third-com>
</div>
`
})
const vm = new Vue({
el: '#app',
data: {
title: 'form father',
}
})
props属性值类型
- 字符串String
- 数值Number
- 布尔值Boolean
- 数组Array
- 对象Object
html代码如下:
<div id="app">
<menu-item :pstr="pstr" :pnum="12" :pbool="true" :parr="parr" :pobj="pobj"></menu-item>
</div>
js代码如下:
Vue.component('menu-item', {
props: ['pstr', 'pnum', 'pbool', 'parr', 'pobj'],
template: `
<div>
<div>{{pstr}}</div>
<div>{{pnum + 1}}</div>
<div>{{pbool}}</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>
`
})
const vm = new Vue({
el: '#app',
data: {
pstr: 'hello',
parr: ['apple', 'banana', 'orange', 'lemon'],
pobj: {
name: '神里',
age: 21
}
}
})
2. 子组件向父组件传值
子组件通过自定义事件向父组件传递信息
<button v-on:click="$emit('enlarge-text')">扩大字体</button>
父组件监听子组件的事件
<menu-item v-on:enlare-text="fontSize += 0.1"></menu-item>
html代码如下:
<div id="app">
<div :style="{fontSize: fontSize + 'px'}">{{msg}}</div>
<menu-item v-on:enlarge-text="handle"></menu-item>
</div>
js代码如下:
Vue.component('menu-item', {
template: `
<div>
<button v-on:click="$emit('enlarge-text')">扩大字体</button>
</div>
`
})
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello',
fontSize: 10
},
methods: {
handle: function () {
this.fontSize += 5;
}
}
})
子组件通过自定义事件向父组件传递数据
html代码如下:
<div id="app">
<div :style="{fontSize: fontSize + 'px'}">{{msg}}</div>
<menu-item v-on:enlarge-text="handle($event)"></menu-item>
</div>
js代码如下:
Vue.component('menu-item', {
template: `
<div>
<button v-on:click='$emit("enlarge-text", 10)'>扩大字体</button>
</div>
`
})
const vm = new Vue({
el: '#app',
data: {
msg: 'hello',
fontSize: 10
},
methods: {
handle: function (val) {
this.fontSize += val;
}
}
})
3. 非父子组件间传值
单独的事件中心管理组件间的通信
var eventHub = new Vue()
监听事件与销毁事件
// 监听事件add-todo事件,并且传递参数addTodo
eventHub.$no('add-todo', addTodo)
// 销毁事件add-todo事件
eventHub.$off('add-todo')
触发事件
// 触发add-todo,并且传递参数id
eventHub.$emit('add-todo', id)
html代码如下:
<div id="app">
<div>父组件</div>
<div>
<button @click="handle">销毁</button>
</div>
<test-tom></test-tom>
<test-jerry></test-jerry>
</div>
js代码如下:
var eventHub = new Vue();
Vue.component('test-tom', {
data: function () {
return {
num: 0
}
},
template: `
<div>
<div>TOM:{{num}}</div>
<div>
<button @click='handle'>点击</button>
</div>
</div>
`,
methods: {
handle: function () {
eventHub.$emit('jerry-event', 2);
}
},
mounted: function () {
eventHub.$on('tom-event', (val) => {
this.num += val;
})
}
})
Vue.component('test-jerry', {
data: function () {
return {
num: 0
}
},
template: `
<div>
<div>JERRY:{{num}}</div>
<div>
<button @click='handle'>点击</button>
</div>
</div>
`,
methods: {
handle: function () {
eventHub.$emit('tom-event', 1);
}
},
mounted: function () {
eventHub.$on('jerry-event', (val) => {
this.num += val;
})
}
})
const vm = new Vue({
el: '#app',
data: {
msg: ''
},
methods: {
handle: function () {
eventHub.$off('tom-event');
eventHub.$off('jerry-event');
}
}
})
五、组件插槽的作用
1. 父组件向子组件传递内容
插槽位置
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>ERROR!</strong>
<slot></slot>
</div>
`
})
const vm = new Vue({
el: '#app',
data: {
msg: ''
}
});
插槽内容
html代码如下:
<div id="app">
<alert-box>Something bad happened.</alert-box>
</div>
2. 具名插槽用法
js代码如下:
Vue.component('base-layout', {
template: `
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`
})
const vm = new Vue({
el: '#app',
data: {
msg: ''
}
});
插槽内容
html代码如下:
<div id="app">
<base-layout>
<h1>标题内容</h1>
<p>主要内容1</p>
<p>主要内容1</p>
<p slot="footer">底部内容</p>
<template slot="header">
<h1>标题内容</h1>
<h1>标题内容</h1>
</template>
</base-layout>
</div>
3. 作用域插槽
- 应用场景:父组件对子组件的内容进行加工处理
- 插槽定义
js代码如下:
Vue.component('fruit-list', {
props: ['lists'],
template: `
<ul>
<li :key="item.id" v-for="item in lists">
<slot v-bind:item='item'>
{{item.name}}
</slot>
</li>
</ul>
`
})
const vm = new Vue({
el: '#app',
data: {
msg: '',
list: [{
id: 1,
name: 'apples'
}, {
id: 2,
name: 'bananas'
}, {
id: 3,
name: 'lemons'
}, {
id: 4,
name: 'pineapples'
}]
}
})
- 插槽内容
html代码如下:
<div id="app">
<fruit-list :lists="list">
<template slot-scope="slotProps">
<strong v-if="slotProps.item.id==2" class="current">
{{slotProps.item.name}}
</strong>
<span v-else>
{{slotProps.item.name}}
</span>
</template>
</fruit-list>
</div>
综合案例
css代码如下:
.container .cart {
width: 300px;
margin: auto;
}
.container .title {
background-color: lightblue;
height: 40px;
line-height: 40px;
text-align: center;
/* color: #fff; */
}
.container .total {
background-color: #FFCE46;
height: 50px;
line-height: 50px;
text-align: right;
}
.container .total button {
margin: 0 10px;
background-color: #DC4C40;
height: 35px;
width: 80px;
border: 0;
}
.container .total span {
color: red;
font-weight: bold;
}
.container .item {
height: 55px;
line-height: 55px;
position: relative;
border-top: 1px solid #ADD8E6;
}
.container .item img {
width: 45px;
height: 45px;
margin: 5px;
}
.container .item .name {
position: absolute;
width: 90px;
top: 0;
left: 55px;
font-size: 16px;
}
.container .item .change {
width: 100px;
position: absolute;
top: 0;
right: 50px;
}
.container .item .change a {
font-size: 20px;
width: 30px;
text-decoration: none;
background-color: lightgray;
vertical-align: middle;
}
.container .item .change .num {
width: 40px;
height: 25px;
}
.container .item .del {
position: absolute;
top: 0;
right: 0;
width: 40px;
text-align: center;
font-size: 40px;
cursor: pointer;
color: red;
}
.container .item .del:hover {
background-color: orange;
}
html代码如下:
<div id="app">
<div class="container">
<my-cart></my-cart>
</div>
</div>
js代码如下:
// 标题组件
var CartTitle = {
props: ['name'],
template: '<div class="title">{{name}}的商品</div>'
}
// 列表组件
var CartList = {
props: ['list'],
template: `
<div>
<div :key="item.id" v-for="item in list" class="item">
<img :src="item.img">
<div class="name">{{item.name}}</div>
<div class="change">
<a href="" @click.prevent="sub(item.id)">-</a>
<input type="text" class="num" :value="item.num" @blur="changeNum(item.id, $event)">
<a href="" @click.prevent="add(item.id)">+</a>
</div>
<div class="del" @click="del(item.id)">x</div>
</div>
</div>
`,
methods: {
changeNum: function (id, event) {
// 子组件通过自定义事件向父组件传递信息(数量)
// 触发事件
this.$emit('change-num', {
id: id,
type: 'change',
num: event.target.value
});
},
sub: function (id) {
this.$emit('change-num', {
id: id,
type: 'sub'
});
},
add: function (id) {
this.$emit('change-num', {
id: id,
type: 'add'
});
},
del: function (id) {
// 把id传递给父组件 触发事件
this.$emit('cart-del', id);
}
}
}
// 结算组件
var CartTotal = {
props: ['list'],
template: `
<div class="total">
<span>总价:{{total}}</span>
<button @click="settlement">结算</button>
</div>
`,
// 计算属性
computed: {
total: function () {
// 计算商品的总价
let t = 0;
this.list.forEach(item => {
t += item.price * item.num;
});
return t;
}
},
methods: {
settlement: function () {
confirm("你所购买的商品总共" + this.total + "钱");
// alert("你所购买的商品总共" + this.total +"钱");
}
}
}
// 购物车组件
Vue.component('my-cart', {
data: function () {
return {
uname: '神里の凌华',
list: [{
id: 1,
img: 'img/1.png',
name: '名称1',
num: 1,
price: 40
}, {
id: 2,
img: 'img/2.jpg',
name: '名称2',
num: 1,
price: 40
}, {
id: 3,
img: 'img/3.jpg',
name: '名称3',
num: 1,
price: 40
}, {
id: 4,
img: 'img/4.png',
name: '名称4',
num: 1,
price: 40
}, {
id: 5,
img: 'img/5.jpg',
name: '名称5',
num: 1,
price: 40
}]
}
},
// 父组件通过props给子组件传值
// 父组件监听子组件的事件(删除)
template: `
<div class="cart">
<cart-title :name="uname"></cart-title>
<cart-list :list="list" @change-num="changeNum($event)" @cart-del="delCart($event)"></cart-list>
<cart-total :list="list"></cart-total>
</div>
`,
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) {
// 当商品为零时停止减一操作
if (item.num == 0) {
alert('商品为零了!');
return true;
}
item.num -= 1;
// 终止遍历
return true;
}
});
} else if (val.type == 'add') {
// 加一操作
this.list.some(item => {
if (item.id == val.id) {
// 先将String转化为number
// item.num -= 0;
// item.num *= 1;
// item.num = +item.num;
item.num = parseInt(item.num);
// 然后在加一
item.num += 1;
// 终止遍历
return true;
}
});
}
},
delCart: function (id) {
// 方法一
this.list.some((item, index) => {
if (item.id == id) {
this.list.splice(index, 1);
return true;
}
});
// 方法二
// 根据id删除list中对应的数据
// 1、找到id所对应数据的索引
// let index = this.list.findIndex(item => {
// // return item.id == id;
// if (item.id == id) {
// return true;
// }
// });
// 2、根据索引删除对应数据
// this.list.splice(index, 1);
}
}
})
const vm = new Vue({
el: '#app',
data: {
}
})