目录
代码放到码云上了,可以自己拉取:https://gitee.com/cxy-xupeng/vue-test.git
一、什么是组件化
有的复杂问题太难解决,我们把复杂问题拆分成多个简单问题,然后解决每个简单问题,都解决好了再整合成一个(有点类似于写多个接口,然后业务里面运用各种接口形成不同的逻辑功能)
组件使用的三个步骤:
- 创建组件构造器
- 注册组件
- 使用组件
二、组件的基本使用
1.自己自定义一个标签
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
</div>
<script src="../js/vue.js"></script>
<script>
//1.创建组件构造器对象
const cpnc = Vue.extend({
template:`
<div>
<h2>标题</h2>
<p>内容,哈哈</p>
<p>内容,呵呵</p>
</div>
`
})
//2.注册组件
Vue.component('my-cpn',cpnc)
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏'
}
})
</script>
</body>
</html>
2.全局组件和局部组件
上述是全局组件,怎么注册局部组件?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<xupeng></xupeng>
</div>
<script src="../js/vue.js"></script>
<script>
//1.创建组件构造器对象
const cpnc = Vue.extend({
template:`
<div>
<h2>标题</h2>
<p>内容,哈哈</p>
<p>内容,呵呵</p>
</div>
`
})
//2.注册组件(全局组件,意味着可以在多个VUE的实例下面使用)
Vue.component('my-cpn',cpnc)
/* 注册局部组件 */
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏'
},
components:{
//xupeng:使用组件时的标签名,cpnc:组件构造器
xupeng:cpnc
}
})
</script>
</body>
</html>
3.父组件和子组件
先创建一个组件1,在创建一个组件2,吧组件1注册在组件2。那么2就是1的父亲。我们把组件2注册在Vue,在使用2的时候就会自动用到组件1。
但是因为只有组件2在Vue中注册,所以vue只认识组件2,并不认识组件1,你直接调用组件1不行,只能通过组件2调用组件1(你士兵的士兵不是你的士兵)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<xupeng2></xupeng2>
</div>
<script src="../js/vue.js"></script>
<script>
//1.创建第一个组件构造器(子组件)
const cpnc1 = Vue.extend({
template:`
<div>
<h2>徐鹏1</h2>
<p>内容,哈哈</p>
<p>内容,呵呵</p>
</div>
`
})
//2.创建第二个组件构造器(父组件)
/* 在第二个组件构造器中注册并使用第一个组件构造器 */
const cpnc2 = Vue.extend({
template:`
<div>
<h2>徐鹏2</h2>
<p>内容,哈哈</p>
<p>内容,呵呵</p>
<xupeng1></xupeng1>
</div>
`,
components:{
xupeng1:cpnc1
}
})
/* 注册局部组件 */
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏'
},
components:{
xupeng2:cpnc2
}
})
</script>
</body>
</html>
4.组件语法糖注册
全局组件,局部组件的语法糖如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<xupeng></xupeng>
<xupeng2></xupeng2>
</div>
<script src="../js/vue.js"></script>
<script>
//全局组件:将创建构造器+注册组件融合到了一起
Vue.component('xupeng',{
template:`
<div>
<h2>徐鹏1</h2>
<p>内容,哈哈</p>
<p>内容,呵呵</p>
</div>
`
})
//局部组件
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏'
},
components:{
'xupeng2':{
template:`
<div>
<h2>徐鹏2</h2>
<p>内容,哈哈</p>
<p>内容,呵呵</p>
</div>
`
}
}
})
</script>
</body>
</html>
5.组件模板抽离写法
大家看了语法糖不知道有没有这个感觉:这语法糖用了反而更乱了。。。反正我是这个感觉
仔细看一下,语法糖减少了Vue.entend的过程,我们觉得乱是因为html写在里面了。所以需要想办法抽离html:
这里有两种方法,我们推荐第二种
(1)通过<script type="text/x-template">写文本
(2)通过<template>写文本
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<!-- 1.通过<script type="text/x-template">将文本写在这里 -->
<script type="text/x-template" id='xupeng1'>
<div>
<h2>{
{title}}</h2>
<p>哈哈</p>
</div>
</script>
<!-- 2.通过<template>将文本写在这里 -->
<template id='xupeng2'>
<div>
<h2>徐鹏2</h2>
<p>呵呵</p>
</div>
</template>
<div id="app">
<xupeng1></xupeng1>
<xupeng2></xupeng2>
</div>
<script src="../js/vue.js"></script>
<script>
Vue.component('xupeng1',{
template:'#xupeng1',
data(){
return{
title:'徐鹏1'
}
}
})
Vue.component('xupeng2',{
template:'#xupeng2'
})
//局部组件
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏'
}
})
</script>
</body>
</html>
6.组件数据的存放
注:组件内部不能访问Vue实例属性!
你组件可以有html,为什么会没有data,还要用vue实例的data?那么多组件谁都来用?而且那么多组件重名怎么办?所以组件内部不能使用vue实例。
注:组件内部的data跟vue的不同,组件的data是一个函数,他的返回值才是我们真正要的数据。
理解:为什么组件中的data必须是函数?
一个页面如果调用多个一样的组件,那么多次调用,我们肯定希望他们每次都独立数据,而不希望调用三套,缺却共用一套数据。
这里就涉及到了块级作用域的概念。在ES5里,只有函数(function)有块级作用域,ES6里新增了let。let中的if和for也是有块级作用域的概念的。
因此,为了确保数据独立性,我们组件里的数据只能使用函数的方式
但是你如果说,我调用多个组件,就希望他们共用一套数据咋办?(纯属杠精问题)
定义一个全局变量,每次返回操作这个全局变量。虽然你每个组件互相隔离,但是如果都跟同一个变量发生关系,间接的所有组件的数据不是都有关系了?
7.父子组件的通信(父传子)
这一块东西稍微有点多,但是有后端基础,仔细看其实也还好。
父=>子:通过props向子组件传递消息(注:不支持驼峰)
父=>子实例1:直接传一个数组
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<xupeng1 :smovies='movies'></xupeng1>
</div>
<template id="xupeng1">
<div>
<ul>
<li v-for="item in smovies">{
{item}}</li>
</ul>
{
{smovies}}
</div>
</template>
<script src="../js/vue.js"></script>
<script>
/* 父传子:props */
const xupeng1 = {
template:'#xupeng1',
props:['smovies'],
data(){
return{}
}
}
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏',
movies:['海贼王','火影忍者','游戏王','名侦探柯南']
},
components:{
//增强写法,等价于xupeng1:xupeng1
xupeng1
}
})
</script>
</body>
</html>
父=>子实例2:类型限制
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<xupeng1 :smovies='movies'></xupeng1>
</div>
<template id="xupeng1">
<div>
<ul>
<li v-for="item in smovies">{
{item}}</li>
</ul>
{
{smovies}}
</div>
</template>
<script src="../js/vue.js"></script>
<script>
/* 父传子:props */
const xupeng1 = {
template:'#xupeng1',
props:{
smovies:Array
},
data(){
return{}
}
}
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏',
movies:['海贼王','火影忍者','游戏王','名侦探柯南']
},
components:{
//增强写法,等价于xupeng1:xupeng1
xupeng1
}
})
</script>
</body>
</html>
父=>子实例3:类型限制+提供默认值
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<xupeng1 :smovies='movies' :smessage='message'></xupeng1>
</div>
<template id="xupeng1">
<div>
<ul>
<li v-for="item in smovies">{
{item}}</li>
</ul>
{
{smovies}}
<br /><br />
{
{smessage}}
</div>
</template>
<script src="../js/vue.js"></script>
<script>
/* 父传子:props */
const xupeng1 = {
template:'#xupeng1',
props:{
/* 高版本,类型是对象或者数组时,默认值必须要是一个函数 */
smovies:{
type:Array,
// default:['默认数组'] 低版本可以这样写
//高版本这样写
default(){
return ['默认数组']
}
},
smessage:{
type:String,
default:'默认字符串',
required:true
}
},
data(){
return{}
}
}
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏',
movies:['海贼王','火影忍者','游戏王','名侦探柯南']
},
components:{
//增强写法,等价于xupeng1:xupeng1
xupeng1
}
})
</script>
</body>
</html>
父=>子实例4:怎么处理驼峰命名
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<xupeng1 :smovies='movies' :s-message='message'></xupeng1>
</div>
<template id="xupeng1">
<div>
<ul>
<li v-for="item in smovies">{
{item}}</li>
</ul>
{
{smovies}}
<br /><br />
{
{sMessage}}
</div>
</template>
<script src="../js/vue.js"></script>
<script>
/* 父传子:props */
const xupeng1 = {
template:'#xupeng1',
props:{
/* 高版本,类型是对象或者数组时,默认值必须要是一个函数 */
smovies:{
type:Array,
// default:['默认数组'] 低版本可以这样写
//高版本这样写
default(){
return ['默认数组']
}
},
sMessage:{
type:String,
default:'默认字符串',
required:true
}
},
data(){
return{}
}
}
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏',
movies:['海贼王','火影忍者','游戏王','名侦探柯南']
},
components:{
//增强写法,等价于xupeng1:xupeng1
xupeng1
}
})
</script>
</body>
</html>
你看,我们直接驼峰是会报错的,但是改成这样就可以了:
主要改的是,父和子关联的时候,需要把驼峰变成“-”的形式
8.父子组件的通信(子传父)
子=>父:通过事件向父组件发送消息
流程:
(1)在子组件中,通过$emit()来触发事件
(2)在父组件中,通过v-on(语法糖@)来监听子组件事件
实例:
你如果看到最后你会问,最后不还是进的父组件方法嘛,为什么要绕这么一大圈呢?
因为仔细看,通过子传父进入父组件方法的时候,会有一个item参数,这个参数是子组件传来的。我们绕了一大圈的目的其实就是为了这个参数
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<!-- 父组件模板 -->
<div id="app">
<cpn @itemclick='cpnClick'></cpn>
</div>
<!-- 子组件模板 -->
<template id="cpn">
<div>
<button v-for="item in categories" @click="btnClick(item)">{
{item.name}}</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template:'#cpn',
data(){
return {
categories:[
{id:'a',name:'徐鹏1'},
{id:'b',name:'徐鹏2'},
{id:'c',name:'徐鹏3'},
{id:'d',name:'徐鹏4'},
]
}
},
methods:{
btnClick(item){
/* 发射事件:自定义事件 */
this.$emit('itemclick',item)
}
}
}
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏'
},
components:{
cpn
},
methods:{
cpnClick(item){
console.log(item)
}
}
})
</script>
</body>
</html>
9.父子的双向绑定
(1)v-model = :value + @input
(2)点击鼠标修改数字的时候,通过子传父传给父组件。父组件监听到这个子传父之后,自己做出修改
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<sonapp :xupengson1='xupengParent1' :xupengson2='xupengParent2'
@xupengparent1change='xupengParent1Change' @xupengparent2change='xupengParent2Change'></sonapp>
{
{xupengParent1}}<br>
{
{xupengParent2}}
</div>
<template id="sonapp">
<div>
<h2>props:{
{xupengson1}}</h2>
<h2>data:{
{sondatanumber1}}</h2>
<input type="text" :value="sondatanumber1" @input="son1input" />
<h2>props:{
{xupengson2}}</h2>
<h2>data:{
{sondatanumber2}}</h2>
<input type="text" :value="sondatanumber2" @input="son2input" />
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
xupengParent1:1,
xupengParent2:2
},
methods:{
xupengParent1Change(value){
this.xupengParent1 = parseInt(value)
},
xupengParent2Change(value){
this.xupengParent2 = parseInt(value)
},
},
components:{
sonapp:{
template:"#sonapp",
props:{
xupengson1:Number,
xupengson2:Number,
},
data(){
return{
sondatanumber1:this.xupengson1,
sondatanumber2:this.xupengson2,
}
},
methods:{
son1input(event){
this.sondatanumber1 = event.target.value;
this.$emit('xupengparent1change',this.sondatanumber1)
},
son2input(event){
this.sondatanumber2 = event.target.value;
this.$emit('xupengparent2change',this.sondatanumber2)
},
}
}
}
})
</script>
</body>
</html>
10.父组件访问子组件
前面我们讲了父子组件之间传输数据,但是父组件、子组件其实都是个对象。是个对象就可以访问这个对象。我们通过父组件访问子组件,拿到子组件对象之后也可以获取值。
父组件访问子组件有两种方式:
(1)$children:获取子标签对象,这种方法不推荐,因为顺序依赖数组下标
(2)$refs:在子标签上得加上ref属性
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn ref='aaa'></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id='cpn'>
<div>子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏'
},
methods:{
btnClick(){
/* 1.$children获取子标签对象,这种方法不推荐,因为顺序依赖数组下标 */
// console.log(this.$children);
// for(let c of this.$children){
// console.log(c.name)
// c.showMessage()
// }
/* 2.推荐使用$refs,但是在子标签上得加上ref属性 */
console.log(this.$refs.aaa)
}
},
components:{
cpn:{
template:'#cpn',
data(){
return{
name:'子组件name'
}
},
methods:{
showMessage(){
console.log('showMessage')
}
}
}
}
})
</script>
</body>
</html>
11.子组件访问父组件(不建议使用)
this.$parent:访问父组件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
<template id='cpn'>
<ccpn></ccpn>
</template>
<template id='ccpn'>
<div>
子子组件
<button @click="btnClick">按钮</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏'
},
components:{
cpn:{
template:'#cpn',
data(){
return{
name:'cpn的name'
}
},
components:{
ccpn:{
template:'#ccpn',
methods:{
btnClick(){
//1.访问父组件
console.log(this.$parent)
console.log(this.$parent.name)
//2.访问根组件
console.log(this.$root.message)
}
}
}
}
}
}
})
</script>
</body>
</html>
三、插槽的使用
1.插槽的基本使用
<slot>标签,该标签可以有默认值。
如果模板里自定义了,就会替代标签内的默认值
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<xupeng></xupeng>
<xupeng><span>哈哈</span></xupeng>
<xupeng><i>呵呵</i><span>哈哈</span></xupeng>
</div>
<template id='xupeng'>
<div>
<h2>徐鹏</h2>
<slot><button>按钮</button></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏'
},
components:{
xupeng:{
template:'#xupeng'
}
}
})
</script>
</body>
</html>
2.具名插槽
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<xupeng>
<span slot="left">替换插槽</span>
<button slot="center">替换按钮</button>
</xupeng>
</div>
<template id='xupeng'>
<div>
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
<slot><span>哈哈</span></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏'
},
components:{
xupeng:{
template:'#xupeng'
}
}
})
</script>
</body>
</html>
3.作用域插槽
编译作用域:父组件模板的所有东西都会在父级作用域编译,子组件模板的所有东西都会在子级作用域编译
作用域插槽:父组件提供插槽的标签,但是内容由子组件来提供
实例:(插槽空间==>插槽数据)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<xupeng></xupeng>
<xupeng>
<template slot-scope='xupengslot'>
<!-- <span v-for="item in slot.data">{
{item}}--</span> -->
<span>{
{xupengslot.xupeng.join(' -- ')}}</span>
</template>
</xupeng>
</div>
<template id='xupeng'>
<div>
<slot :xupeng="language">
<ul>
<li v-for="item in language">{
{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'你好,徐鹏'
},
components:{
xupeng:{
template:'#xupeng',
data(){
return{
language:['java','linux','python']
}
}
}
}
})
</script>
</body>
</html>