一. 组件
a. 定义
- 组件就是扩展自定义 的html标签
- 组件是一个功能的集合,可以重复的使用
- 组件能使一个复杂的系统分割为一些简单的组件
- 组件使前端分工协作更加方便
- 组件在小项目中会显得复杂,项目越大,优势就越明显
b. 组件名(大小写)
定义组件名的方式有两种:
- 使用 kebab-case
<my-component-name> - 使用 PascalCase
<MyComponentName>
c. 使用
全局组件:它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。
Vue.component(组件名,{
template:`<div>组件的模板里面只能够有一个根组件</div>`
})
<组件名></组件名> //使用组件
局部组件:
1. 定义组件
let counter = {
template:`<div>只能有一个根节点{{num}}</div>`,
data:function(){ return {num:1}}
methods:{}
}
2. 注册组件
new Vue({
components:{counter,...}
})
3. 使用组件
<counter></counter>
d. 组件扩展:模块系统
在模块系统中局部注册
如果你还在阅读,说明你使用了诸如 Babel 和 webpack 的模块系统。在这些情况下,我们推荐创建一个 components
目录,并将每个组件放置在其各自的文件中。
然后你需要在局部注册之前导入每个你想使用的组件。例如,在一个假设的 ComponentB.js
或 ComponentB.vue
文件中:
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
export default {
components: {
ComponentA,
ComponentC
},
// ...
}
现在 ComponentA
和 ComponentC
都可以在 ComponentB
的模板中使用了。
基础组件的自动化全局注册
可能你的许多组件只是包裹了一个输入框或按钮之类的元素,是相对通用的。我们有时候会把它们称为基础组件,它们会在各个组件中被频繁的用到。
所以会导致很多组件里都会有一个包含基础组件的长列表:
import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'
export default {
components: {
BaseButton,
BaseIcon,
BaseInput
}
}
而只是用于模板中的一小部分:
<BaseInput
v-model="searchText"
@keydown.enter="search"
/>
<BaseButton @click="search">
<BaseIcon name="search"/>
</BaseButton>
幸好如果你使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),那么就可以使用 require.context
只全局注册这些非常通用的基础组件。这里有一份可以让你在应用入口文件 (比如 src/main.js
) 中全局导入基础组件的示例代码:
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
// 其组件目录的相对路径
'./components',
// 是否查询其子目录
false,
// 匹配基础组件文件名的正则表达式
/Base[A-Z]\w+\.(vue|js)$/
)
requireComponent.keys().forEach(fileName => {
// 获取组件配置
const componentConfig = requireComponent(fileName)
// 获取组件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 获取和目录深度无关的文件名
fileName
.split('/')
.pop()
.replace(/\.\w+$/, '')
)
)
// 全局注册组件
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})
2. 案例-全局组件
a. 全局组件-textvue组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件</title>
<script src="./js/vue.js"></script>
<style>
.modal{
position: absolute;
left: 0;
top:0;
right: 0;
bottom:0;
background-color: rgba(0,0,0,.7);
}
.content{
width: 400px;
height: 300px;
position: absolute;
left:50%;
top:50%;
transform: translate(-50%,-50%);
background-color: #fff;
border-radius: 4px;
overflow: hidden;
}
.title{
line-height: 38px; text-align: center;
height: 38px;
background-color:dodgerblue;
color: #fff;
}
</style>
</head>
<body>
<div id="app">
<!-- 第三步使用组件 -->
<button @click="flag=1">武汉</button> <button @click="flag=2">中国</button>
<model v-if="flag==1" @close="flag=null" maskclose="true">
<p slot="title">武汉加油</p>
<p slot="content">全中国人民都在支持你,4月花开我,来碗热干面</p>
</model>
<model v-if="flag==2" @close="flag=null">
中国加油
</model>
<counter></counter>
<counter></counter>
<counter></counter>
<!-- <child :pnum="pnum" @numchange="pnum=$event"></child>
<child ></child>
<child :pnum="8"></child> -->
<!-- <child :pnum="'abc'"></child> -->
<!-- <h1>{{pnum}}</h1> -->
<div v-for="(item,index) in list ">
{{item.name}} <child :pnum="item.num" @numchange="item.num=$event"></child>
</div>
<h1>{{ totalNum}}</h1>
</div>
<script>
var model = {
props:{
maskclose:{default:false}
},
template:` <div class="modal" @click="maskclose?$emit('close',1):''">
<div class="content">
<div class="title">
<slot name="title"></slot>
</div>
<slot name="content"></slot>
<br><button @click="$emit('close',1)">x</button>
<br> <slot></slot>
</div>
</div>`
}
var counter = {
template:`<div>
<button @click="reduce">-</button>
<input v-model.number="num"/>
<button @click="add">+</button>
</div>`,
data:function(){return {
num:1, // num初始值
step:1, // 每次步进值
max:999, // 最大限定
min:1 // 最小限定
}},
methods:{
add(){
this.num+=this.step; // 单击加 step 1
if(this.num>this.max){this.num=this.max} // 如果超过最大值 等于最大值
},
reduce(){
this.num-=this.step;
if(this.num<this.min){this.num=this.min}
}
}
}
// 第一步定义组件
var child = {
template:`<span><button @click="add()">{{num}}</button></span>`,
props:
{
pnum:{
default:1,
},
},
data(){return {num:this.pnum}},
methods:{
add(){
this.num++;
this.$emit("numchange",this.num)
}
}
}
new Vue({
el:"#app",
created() {
this.$on("numchange",function(e){console.log(e)})
},
data:{
list:[{name:"pt",num:2},{name:"xj",num:4}],
pnum:5,
flag:null,
},
computed:{
totalNum(){
return this.list.reduce((a,b)=>a.num+b.num)
}
},
// 第二步注册组件
components:{
counter,
child,
model,
}
})
</script>
</body>
</html>
b. 全局组件2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 第三步使用组件 -->
<counter></counter>
<counter></counter>
<counter></counter>
</div>
<script>
var counter = {
template:`<div>
<button @click="reduce">-</button>
<input v-model.number="num"/>
<button @click="add">+</button>
</div>`,
data:function(){return {
num:1,
// num初始值
step:1,
// 每次步进值
max:999,
// 最大限定
min:1
// 最小限定
}},
methods:{
add(){
this.num+=this.step;
// 单击加 step 1
if(this.num>this.max){this.num=this.max}
// 如果超过最大值 等于最大值
},
reduce(){
this.num-=this.step;
if(this.num<this.min){this.num=this.min}
}
}
}
// 第一步定义组件
new Vue({
el:"#app",
data:{
},
// 第二步注册组件
components:{
counter
}
})
// 1. 组件就是扩展自定义 的html标签
// 2. 组件是一个功能的集合,可以重复的使用
// 3. 组件能使一个复杂的系统分割为一些简单的组件
// 4. 组件使前端分工协作更加方便
// 5. 组件在小项目中会显得复杂,项目越大,优势就越明显
// 6. 全局组件
</script>
</body>
</html>
3. 组件数据传递-父传子
props 组件的数据传递
- 父亲传递给子元素
a. props属性传参的方法
b. 父亲传递给子元素的参数是单向数据流(只读)
c. 喜欢修改父亲传递过来参数 data(){return {num:this.pnum}}
<steper :pnum="p1" @numchange="item.num=$event">
const steper = {
template: `<div @click=“$emit('numchange',num)”></ div>`,
props:['pnum'],
data(){return {num:this.pnum}}
}
- 子元素传递给父元素
a. 事件方式向父亲元素传递
b. 父亲通过 v-on事件名=“” 接收数据
3.子元素接收参数的形式props
props:['pnum','min',...]
prpos:{
pnum:{
type:[Number,String,Boolean,Array,Object],
default:5
// 设置默认值
}
}
案例1-组件间数据的传递
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件数据的传递-子组件给父亲组件发送事件</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="app">
<steper :pnum="p1"></steper>
<steper :pnum="p2"></steper>
<h2>{{p1}}</h2>
<h2>{{p2}}</h2>
</div>
<script>
// 1.定义组件
const steper = {
template:`<div style="display:inline-block">
<input type="text" v-model="num"/>
<button @click="num++">+</button>
</div>`,
// 在模板里面可以使用pnum
data(){return {num:this.pnum}},
props:["pnum"]
// 通过props来接受父亲传递过来的参数 pnum
// props 传递过来的数据只读,不能进行双向绑定v-model 和修改
}
new Vue({
el:"#app",
data:{
p1:15,
p2:8
},
// 2.注册组件
components:{steper}
})
</script>
</body>
</html>
案例2-组件间数据的传递(综合)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件数据的传递-子组件给父亲组件发送事件</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="app">
<h1>购物车</h1>
<div v-for="(item,index) in list" :key="index">
<span>{{item.name}}</span>
<span>{{item.price}}</span>
<steper :pnum="item.num" @numchange="item.num=$event" ></steper>
</div>
<h2>总数量:{{allNum}}</h2>
<h2>总价格:{{allPrice}}</h2>
</div>
<script>
// 1.定义组件
const steper = {
template:`<div style="display:inline-block">
<button @click="num--;$emit('numchange',num)">-</button>
<input type="text" v-model="num"/>
<button @click="add()">+</button>
</div>`,
// 在模板里面可以使用pnum
data(){return {num:this.pnum}},
props:{
pnum:{
default:1,
// type:Number String,Boolean Array
}
},
methods:{
add(){
this.num++;
this.$emit("numchange",this.num)
}
}
// 通过props来接受父亲传递过来的参数 pnum
// props 传递过来的数据只读,不能进行双向绑定v-model 和修改
}
new Vue({
el:"#app",
data:{
list:[
{name:"红宝书",num:3,price:24},
{name:"蓝宝书",num:1,price:34},
{name:"绿宝书",num:6,price:18},
{name:"彩宝书",num:5,price:22},
]
},
computed:{
allNum(){
let n=0;
this.list.forEach(item=>{
n+=item.num;
})
return n;
},
allPrice(){
let n = 0;
this.list.forEach(item=>{
n+=item.num*item.price;
})
return n;
}
},
// 2.注册组件
components:{steper}
})
</script>
</body>
</html>
4. 组件的插槽
1.基础
<slot> 在组件内部 嵌套的内容都会被插入到这个标签里面
2.命名插槽
a. 调用组件
<modal>
<div slot="title">标题</title>
<div slot="content">内容</title>
</modal>
b. 组件内部
<div class="title">
<slot name="title"></slot>
</div>
<div class="content">
<slot name="content"></slot>
</div>
案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件的插槽</title>
<script src="./js/vue.js"></script>
<style>
.block{ border: 1px solid #f0f0f0;}
.title{ color:#fff;background-color: dodgerblue;}
</style>
</head>
<body>
<div id="app">
<block>
<p slot="title">中国加油</p>
<div slot="content"> 中国是中华人民共和国</div>
</block>
<block>
<p slot="title">武汉加油</p>
<div slot="content"> 全中国人民都等着吃热干面呢。</div>
<button>确定</button>
</block>
</div>
<script>
const block = {
template:`
<div class="block">
<div class="title">
<slot name="title"></slot>
</div>
<div class="content">
<slot name="content"></slot>
</div>
<slot></slot>
</div>`
}
new Vue({
el:"#app",
data:{
},
components:{block}
})
// 1. vue组件像html标签一样
</script>
</body>
</html>
组件插槽.png
5. 弹出框
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>组件的插槽</title>
<script src="./js/vue.js"></script>
<style>
*{margin: 0; padding: 0;}
body{padding: 50px;}
.modal{
position: absolute;
left: 0; top:0; bottom:0; right:0;
background-color: rgba(0,0,0,.6);}
.modal .content{
width: 400px; height: 300px;
position: absolute; left:50%; top:50%;
transform: translate(-50%,-50%);
background-color: #fff; }
.modal .title{
line-height: 48px; color:#fff;
background-color:rgb(9, 97, 238);
position: relative; text-align: center; }
.modal .close{
position: absolute;
line-height: 48px;
width: 48px; right:0; top:0;
text-align: center; cursor: pointer; }
.modal .body{
padding:15px;
}
</style>
</head>
<body>
<div id="app">
<button @click="num=1">武汉</button> <button @click="num=2">中国</button> <br>
<modal v-if="num==1" @close="num=null">
<span slot="title">武汉加油</span>
<div slot="content">待到四月花开,热腾腾热干面,走起来</div>
</modal>
<modal v-if="num==2" @close="num=null" >
<span slot="title">中国加油</span>
<div slot="content">中国是一个5千年历史大国,什么困难都遇到过,都能迈过去!</div>
</modal>
</div>
<script>
const modal = {
template:`
<div class="modal" >
<div class="content">
<div class="title"><slot name="title"></slot> <span class="close" @click="$emit('close',null)">×</span></div>
<div class="body"><slot name="content"></slot></div>
</div>
</div>
`,
}
new Vue({
el:"#app",
data:{
num:null,
},
components:{modal}
})
</script>
</body>
</html>
6.综合案例(书单购物车)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表格修改</title>
<style>
*{margin: 0; padding: 0;}
body{padding: 50px;}
.modal{
position: absolute;
left: 0; top:0; bottom:0; right:0;
background-color: rgba(0,0,0,.6);}
.modal .content{
width: 400px; height: 300px;
position: absolute; left:50%; top:50%;
transform: translate(-50%,-50%);
background-color: #fff; }
.modal .title{
line-height: 48px; color:#fff;
background-color:rgb(9, 97, 238);
position: relative; text-align: center; }
.modal .close{
position: absolute;
line-height: 48px;
width: 48px; right:0; top:0;
text-align: center; cursor: pointer; }
.modal .body{
padding:15px;
}
.table{ border-collapse: collapse; width: 800px;}
.table td{ border:1px solid #f0f0f0; line-height: 38px; text-align: center;}
.table .th{ background-color: dodgerblue; color:#fff}
</style>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="app">
<input type="text" placeholder="标题" v-model="newItem.title">
<input type="text" placeholder="作者" v-model="newItem.author">
<input type="date" placeholder="日期" v-model="newItem.date">
<button @click="addItem">确定</button>
<!-- 单击确定执行 addItem 添加新的条目到list里面 -->
<table class="table">
<tr class="th"><td>序号</td>
<td>标题</td>
<td>作者</td>
<td>日期</td>
<td>操作</td></tr>
<tr v-for="(item,index) in list" :key="index">
<td>{{index+1}}</td>
<td>{{item.title}}</td>
<td>{{item.author}}</td>
<td>{{item.date}}</td>
<td><span @click="delItem(item)">删除</span> | <span @click="toEdit(item,index)">编辑</span></td>
<!-- 单击删除 执行删除命令 | 单击编辑设置flag为true 也就显示了编辑弹框 -->
</tr>
</table>
<!-- 编辑弹窗是显示有flag值来控制 -->
<div class="modal" v-if="flag">
<div class="content">
<div class="title">
编辑
<div class="close" @click="flag=false">×</div>
<!-- 单击x设置flag为false 关闭了编辑弹框 -->
</div>
<div class="body">
<input type="text" placeholder="标题" v-model="tempItem.title"> <br>
<input type="text" placeholder="作者" v-model="tempItem.author"> <br>
<input type="date" placeholder="日期" v-model="tempItem.date"> <br>
<button @click="sureEdit">确定</button><button @click="flag=false">取消</button>
</div>
</div>
</div>
</div>
<script>
new Vue({
el:"#app",
data:{
cIndex:null,//代表当前正在编辑的行数
flag:false,//控制是否显示编辑弹框
newItem:{title:'',author:'',date:''},// 绑定新添加的item条目
tempItem:{title:'',author:'',date:''},// 绑定正则编辑的item条目
list:[ // 列表数据
{title:"水知道答案",author:"mumu",date:"2019-02-09"},
{title:"前端的密码",author:"zql",date:"2019-03-15"}
]
},
methods:{
// 确定编辑
sureEdit(){
this.list[this.cIndex] = {...this.tempItem};
// console.log(this.list[this.cIndex]);
// 把当前正在编辑的行 赋值为 tempItem解构后的值
this.flag = false;
// 关闭编辑器
},
// 准备编辑 -打开编辑弹框
toEdit(item,index){
this.tempItem = {...item};
// 设置tempItem的值为 传过来的Item值
this.cIndex = index;//设置当前编辑的行数为传递过来的index
this.flag = true;//显示编辑框
},
// 添加item
addItem(){
// 把新的条目展开 创建一个新的对象插入到list最前面
this.list.unshift({...this.newItem});
// 清空newItem
this.newItem={title:'',author:'',date:''};
},
// 删除item
delItem(item){
let ind = this.list.indexOf(item);
this.list.splice(ind,1);
}
}
})
</script>
</body>
</html>
表格书单.png