Vue组件化开发入门
可以一个完整的页面分成很多个组件,每个组件实现一个功能模块,每一个组件又可以细分
一、Vue的组件化思想
Vue提供了一种抽象,可以开发复用的小组件来构造应用,每一个应用都可以被抽象成一颗组件树
二、注册组件的步骤
step 1:通过Vue.extend()创建组件构造器
step2 :注册组件Vue.component()注册组件
step3 :使用
三、组件简单实现:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 使用 -->
<my-cpn></my-cpn>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//1 创建组件构造器对象
let cpnC=Vue.extend({
template:`
<div>
<h2>组件</h2>
</div>
`
})
//2 注册组件,字后用什么标签使用组件
Vue.component('my-cpn',cpnC)
var app=new Vue({
el:'#app',
data:{},
methods:{}
})
</script>
</body>
</html>
在实现时碰到了一个问题:
vue.js:634 [Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the “name” option.
这是因为第一次写这串代码时,组件名定义为my-Cpn
Vue.component('my-Cpn',cpnC)
使用时:
<!-- 使用 -->
<my-Cpn></my-Cpn>
这个报错的问题在于html的标签是不区分大小写的,所以其实html读到的标签不是<my-Cpn>
而是my-cpn
,但是js代码区分大小写,所以会出现<my-cpn>
标签未注册的问题,解决方式是在注册组件时不要出现大写字母,如果一定要定义大写字母,在html中使用时也要写成小写
vue.extend()
- 调用Vue.extend()创建的是一个组件构造器
- 通常在创建组件构造器的时候,传入template代表我们自定义组件的模板
- 模板就是使用组件的地方要显示HTML的代码
vue.component()
调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起了一个组件的标签名称,所以要传递两个参数:组件的标签名,组件构造器
四、全局组件和局部组件
在某个Vue实例里注册的组件时局部组件,在Vue实例外面注册的组件时全局组件
全局组件的注册如上所示,下面是局部组件的注册
<script>
//1 创建组件构造器对象
let cpnC=Vue.extend({
template:`
<div>
<h2>组件</h2>
</div>
`
})
//2 注册组件,字后用什么标签使用组件,全局注册
Vue.component('my-cpn',cpnC)
var app=new Vue({
el:'#app',
data:{},
methods:{},
components:{
'cpn':cpnC
}
})
</script>
五、父组件和子组件
<script>
//1 创建组件构造器对象
let cpnC1=Vue.extend({
template:`
<div>
<h2>组件1</h2>
</div>
`
})
const cpnC2=Vue.extend({
template:`
<div>
<h2>组件2</h2>
<cpn1></cpn1>
</div>`
,
components:{
cpn1:cpnC1//子组件cpnC1在这里注册,cpn1只能在cpn2中用
}
})
var app=new Vue({
el:'#app',
data:{},
methods:{},
components:{
cpn2:cpnC2//cpnC2在这里注册,但是页面中可以显示cpnC1的内容
}
})
</script>
六、组件注册的语法糖
实际还是调用了Vue.extend
,但是不用写出来
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<cpn1></cpn1>
<cpn2></cpn2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//全局组件注册的语法糖
Vue.component('cpn1',{
template:`
<div>
<h2>组件1</h2>
</div>
`
})
var app=new Vue({
el:'#app',
data:{},
methods:{},
components:{
'cpn2':{
template:`
<div>
<h2>组件2</h2>
</div>
`}
}
})
</script>
</body>
</html>
七、模板的分离写法
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<cpn1></cpn1>
</div>
<!--方式1:用x-template-->
<script type="text/x-template" id="cpn_1">
<div>
<h2>组件1</h2>
</div>
</script>
<!--方式2:用template标签-->
<template id="cpn_2">
<div>
<h2>组件2</h2>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//注册时用id
Vue.component('cpn1',{
template:'#cpn_2'
})
var app=new Vue({
el:'#app',
data:{},
methods:{},
components:{
// cpn2:'#cpn_2'
}
})
</script>
</body>
</html>
八、组件访问Vue实例的数据
Vue的组件中不能访问Vue实例中的数据,每一个组件有自己的data属性,但是data属性不可以是对象,而要以函数的形式return一个对象,对象内保存了属性
同时在组件中也有自己的methods等其他属性
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<cpn1></cpn1>
</div>
<template id="cpn_1">
<div>
<h2>当前计数{{count}}</h2>
<button type="button" @click="sub">-</button>
<button type="button" @click="add">+</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('cpn1',{
template:'#cpn_1',
data(){
return{
count:0
}
},
methods:{
add:function(){
this.count++
},
sub:function(){
this.count--
}
}
})
var app=new Vue({
el:'#app',
data:{},
methods:{},
components:{
}
})
</script>
</body>
</html>
8.1组件中的data为什么要是一个函数
data:{}
这样写实际上是将data指向一个对象,data保存了这个对象的地址,组件在复用时是创建了一个组件的事例,但是如果实例中的data都指向了同一个对象地址,那么会造成更改一个事例里的数据,别的实例里的数据都被更改了。如果data(){}
这样写,则是使用一个data函数,每一个实例都有自己的函数,自己return回去的对象,这样能够将实例之间的数据隔开,各用各的数据
九、父子组件之间的通讯
9.1通过props父组件向子组件传递数据
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 使用 -->
<cpn1 :cmovies="movies"></cpn1><!--在实例化子组件时进行参数绑定-->
</div>
<template id="cpn1">
<div>
<h2>{{cmovies}}</h2><!--子组件的模板使用子组件中的数据-->
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let cpn1={
template:'#cpn1',
props:['cmovies'],//在子组件中定义数据
data(){
return {}
}
}
var app=new Vue({
el:'#app',
data:{//父组件中的数据定义
movies:["爱丽丝梦游仙境","网球王子","喜羊羊与灰太狼"]
},
methods:{},
components:{
cpn1//子组件在父组件中定义注册
}
})
</script>
</body>
</html>
多个组件套娃传递
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 使用 -->
<cpn2 :fathermovies="movies"></cpn2>
</div>
<template id="cpn2">
<div>
<h2>父组件标题</h2>
<div>
<h2>子组件内容</h2>
<cpn1 :sonMovies="fathermovies"></cpn1>
</div>
</div>
</template>
<template id="cpn1">
<div>
<h3>子组件标题</h3>
<h4>{{sonmovies}}</h4>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let cpn1={
template:'#cpn1',
props:['sonmovies'],
data(){
return {}
}
}
let cpn2={
template:'#cpn2',
props:['fathermovies'],
data(){
return {}
},
components:{
cpn1:cpn1
}
}
var app=new Vue({
el:'#app',
data:{
movies:["爱丽丝梦游仙境","网球王子","喜羊羊与灰太狼"]
},
methods:{},
components:{
cpn2
}
})
</script>
</body>
</html>
props支持哪些类型
也可以这样来写,这样写可以限制参数的类型和默认值
let cpn2={
template:'#cpn2',
props:{
message:{
type:String,//限制类型
default:["a","b"],//设置默认值
required:true//true表示这个值是必须的
}
},
data(){
return {}
},
components:{
cpn1:cpn1
}
}
type可以限定为多种,多个要用(type1,type2)
,自定义类型也可以用来验证,还可以用propF:{func:function(n){//验证函数}}
来进行自己想要的验证
当props里的对象类型为array,那么deafult值不可以写成default:[]
,而要写成函数形式defualt(){return []}
验证支持的数据类型:String,Number,Boolean,Array,Object,Data,Function,Symbol
9.2自定义事件,从子组件向父组件传递数据
基本架构:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
<!-- 父组件模板 -->
<template id="cpn">
<div>
<lpannel></lpannel>
</div>
</template>
<!-- 子组件模板 -->
<template id="leftPannel">
<div>
<button type="button" v-for="item in categories" @click="sendData(item)">{{item.name}}</button>
</div>
</template>
<template id="rightPannel">
<div>
<h2>{{bookName}}</h2>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
<!-- 子组件定义 -->
const leftPannel={
template:'#leftPannel',
data(){
return{
categories:[
{id:1,name:"源氏物语"},
{id:2,name:"庄周"},
{id:3,name:"霍乱时期的爱情"},
{id:4,name:"简爱"},
{id:5,name:"傲慢与偏见"}
]
}
},
methods:{
sendData:function(item){
//传递事件位置
}
}
}
const cpn={
template:'#cpn',
components:{
'lpannel':leftPannel
}
}
var app=new Vue({
el:'#app',
data:{},
methods:{},
components:{
cpn
}
})
</script>
</body>
</html>
在//传递事件位置
写入要传递的事件和数据,使用this.$emit('事件名称',事件参数)
在子组件的注册函数中:
const leftPannel={
template:'#leftPannel',
data(){
return{
categories:[
{id:1,name:"源氏物语"},
{id:2,name:"庄周"},
{id:3,name:"霍乱时期的爱情"},
{id:4,name:"简爱"},
{id:5,name:"傲慢与偏见"}
]
}
},
methods:{
sendData:function(item){
//发射事件
console.log("send")
this.$emit('itemclick',item)
}
}
}
在父组件的模板中:
<template id="cpn">
<div>
<!-- 在父组件中监听事件 -->
<lpannel @itemclick="cpnSendName"></lpannel>
</div>
</template>
在父组件的注册函数中:
const cpn={
template:'#cpn',
components:{
'lpannel':leftPannel
},
methods:{
// 父组件中接收后执行的函数
cpnSendName:function(item){
console.log(item)
}
}
}
执行效果:
完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<cpn ></cpn>
</div>
<!-- 父组件模板 -->
<template id="cpn">
<div>
<!-- 在父组件中监听事件 -->
<lpannel @itemclick="cpnSendName"></lpannel>
</div>
</template>
<!-- 子组件模板 -->
<template id="leftPannel">
<div>
<button type="button" v-for="item in categories" @click="sendData(item)">{{item.name}}</button>
</div>
</template>
<template id="rightPannel">
<div>
<h2>{{bookName}}</h2>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 子组件注册函数
const leftPannel={
template:'#leftPannel',
data(){
return{
categories:[
{id:1,name:"源氏物语"},
{id:2,name:"庄周"},
{id:3,name:"霍乱时期的爱情"},
{id:4,name:"简爱"},
{id:5,name:"傲慢与偏见"}
]
}
},
methods:{
sendData:function(item){
//发射事件
console.log("send")
this.$emit('itemclick',item)
}
}
}
// 父组件注册函数
const cpn={
template:'#cpn',
components:{
'lpannel':leftPannel
},
methods:{
// 父组件中接收后执行的函数
cpnSendName:function(item){
console.log(item)
}
}
}
var app=new Vue({
el:'#app',
data:{},
methods:{
},
components:{
cpn
}
})
</script>
</body>
</html>
物语"},
{id:2,name:“庄周”},
{id:3,name:“霍乱时期的爱情”},
{id:4,name:“简爱”},
{id:5,name:“傲慢与偏见”}
]
}
},
methods:{
sendData:function(item){
//发射事件
console.log(“send”)
this.$emit(‘itemclick’,item)
}
}
}
// 父组件注册函数
const cpn={
template:’#cpn’,
components:{
‘lpannel’:leftPannel
},
methods:{
// 父组件中接收后执行的函数
cpnSendName:function(item){
console.log(item)
}
}
}
var app=new Vue({
el:’#app’,
data:{},
methods:{
},
components:{
cpn
}
})
</script>
</body>
## 十、父子访问
10.1父访问子
$children
this.$children 是一个数组类型,它包含了所有子组件的对象
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<button type="button" @click="btnClick">显示child</button>
</div>
<template id="cpn">
<div id="">
我是子组件
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app=new Vue({
el:'#app',
data:{},
methods:{
btnClick:function(){
console.log(this.$children)
//children里会有很多子组件所以要用下标来访问某个子组件的数据
this.$children[0].showMessage()
console.log(this.$children[0].name)
}
},
components:{
cpn:{
template:'#cpn',
methods:{
showMessage:function(){
console.log("子组件showMessage")
}
},
data(){
return {
name:"我是乔木"
}
}
}
}
})
</script>
</body>
</html>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M4qkN2ce-1592214353129)(C:\Users\93221\AppData\Roaming\Typora\typora-user-images\image-20200615172143404.png)]
$refs
$refs
是一个对象类型,默认是一个空对象
首先在子组件的实例化时定义一个ref属性,这个相当于子组件实例的一个key
<div id="app">
<cpn ref="key"></cpn>
<button type="button" @click="btnClick">显示child</button>
</div>
在父组件访问这个实例时只用.$refs.key.name
就可以访问子组件里的数据或函数
btnClick:function(){
console.log(this.$refs)
this.$refs.key.showMessage()
}
效果
子访问父
$parent
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
<template id="ccpn">
<div>
我是ccpn子组件
<button type="button" @click="btnClick">访问父组件</button>
</div>
</template>
<template id="cpn">
<div>
<h2>我是cpn</h2>
<ccpn></ccpn>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app=new Vue({
el:'#app',
data:{},
methods:{
},
components:{
cpn:{
template:'#cpn',
data(){
return {
name:"我是cpn组件的name"
}
},
methods:{
showMessage:function(){
console.log("cpn组件的showMessage")
}
},
components:{
ccpn:{
template:'#ccpn',
methods:{
btnClick:function(){
console.log(this.$parent.name)
this.$parent.showMessage()
}
},
data(){
return {
name:"我是乔木"
}
}
}
}
}
}
})
</script>
</body>
</html>
$root
访问根,和上述标识符用法相同