vue学习之组件化开发
1、认识组件化
1.1、什么是组件化
-
人们面对复杂问题的处理方式
- 任何一个人处理信息的逻辑能力都是有限的
- 所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容
- 但是我们有一种天生的能力,就是将问题进行拆解
- 如果将一个复杂的问题拆分很多歌可以处理的小问题,再将其放在整体中,你会发现大的问题也会迎刃而解
-
组件化也是类似的思想
- 如果我们将一个页面中所有的处理逻辑全部放在一起处理起来就会变得非常复杂,而且不利于后续的管理和扩展
- 如果我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护变得非常容易
1.2、Vue组件化思想
- 组件化是vue.js中的重要思想
- 它体用了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
- 任何的应用都会被抽象成一颗组件树
-
组件化思想的应用
- 有了组件化的思想,我们在之后的开发中要充分利用它
- 尽可能将页面拆分成一个个小的、可复用的组件
- 这样让我们的代码更加方便组件和管理,并且扩展性更强
2、组件化基础
2.1、注册组件
注册组件的基本步骤
-
组件的使用分为三个步骤
-
看一下通过代码如何注册组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>01-组件化的基本使用</title>
</head>
<body>
<div id="app">
<my-cpn/>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
const cpnC = Vue.extend({
template:`
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈</p>
<p>我是内容,嘿嘿</p>
</div>
`
});
Vue.component('my-cpn',cpnC);
const app = new Vue({
el:"#app",
data:{
message:"小朋友,你是否有很多问号"
}
})
</script>
</body>
</html>
注册组件步骤解析
-
Vue.extend()
- 调用Vue.extend()创建的是一个组件构造器
- 通常在创建组件构造器时,传入template代表我们自定义组件的模板
- 该模板就是我们在使用组件的地方,要显示的HTML代码
- 事实上,这种写法Vue2.x文档中几乎看不到了,它会直接使用我们下面讲到的语法糖,但是很多资料还是会提及这种方式,而且这种方式是学习后面的基础
-
Vue.component()
- 调用该方法是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称
- 所以需要传递两个参数:1、注册组件的标签名 2、组件构造器
-
组件必须挂在在某个Vue实例上,否则不会生效
第三步的解析
组件其他内容
全局组件和局部组件
- 全局组件:当我们通过调用Vue.component()注册组件时,组件的注册是全局的
这意味着该组件可以在任意Vue示例下使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<cpn><cpn>
</div>
<div id="app1">
<cpn></cpn>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
const cpnCom = Vue.extend({
template: `
<div>
<h2>这是组件标题</h2>
<span>组件内容</span>
</div>
`
});
Vue.component('cpn', cpnCom);
const app = new Vue({
el:"#app",
data:{
msg:"看看"
}
});
const app1 = new Vue({
el:"#app1"
})
</script>
</body>
</html>
- 局部组件:如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<cpn><cpn>
</div>
<div id="app1">
<cpn></cpn>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
const cpnCom = Vue.extend({
template: `
<div>
<h2>这是组件标题</h2>
<span>组件内容</span>
</div>
`
});
const app = new Vue({
el:"#app",
data:{
msg:"看看"
},
components:{
cpn:cpnCom
}
});
const app1 = new Vue({
el:"#app1"
})
</script>
</body>
</html>
父组件和子组件
注册组件语法糖
说明
- vue为了简化这个过程,提供了注册的语法糖
- 主要是省却了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替
模板的分离写法
- 我们通过语法糖简化了vue组件的注册过程,另外还有一个地方写法比较麻烦,就是template模板写法
- 如果我们能将其中html分离出来,然后挂载对应组件上,结构必然清晰明了
- 有两种方案定义HTML模板内容
使用script标签
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>05-模板分离方式</title>
</head>
<body>
<div id="app">
<cpn />
</div>
<script type="text/x-template" id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
</script>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
Vue.component("cpn", {
template: "#cpn",
});
const app = new Vue({
el: "#app",
});
</script>
</body>
</html>
使用template标签
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>05-模板分离方式</title>
</head>
<body>
<div id="app">
<cpn />
</div>
<template id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
Vue.component("cpn", {
template: "#cpn",
});
const app = new Vue({
el: "#app",
});
</script>
</body>
</html>
2.2、数据传递
组件可以访问vue实例数据吗
- 组件是一个单独功能模块的封装,这个模块有属于自己的HTML模板,也应该有属于自己的数据data.
- 分析组件中数据存放在哪里
- 不妨来测试组件中能不能访问vue实例中data
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>06-测试组件访问vue实例数据</title>
</head>
<body>
<div id="app">
<my-cpn></my-cpn>
</div>
<template id="myCpn">
<div>{{msg}}</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
const app = new Vue({
el:"#app",
data:{
msg:"消息"
}
components:{
'my-cpn':{
template:'#myCpn'
}
}
})
</script>
</body>
</html>
解析:组件去访问msg,msg定义在vue中我们发现最终并没有发现显示结果,结论组件是不能直接访问vue实例中的data数据;我们发现不能访问,即使可以访问,如果将所有的数据都放在vue实例中,vue实例变得非常臃肿;总而言之,vue组件应该有自己保存数据的地方。
组件数据的存放
- 那组件自己的数据存放在哪里呢?
- 组件对象也有一个data属性(也可以有methods等属性,下面用到)
- 只是这个data属性必须是一个函数
- 并且这个函数返回一个对象,对象里面存放数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>06-测试组件访问vue实例数据</title>
</head>
<body>
<div id="app">
<my-cpn></my-cpn>
</div>
<template id="myCpn">
<div>{{msg}}</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
const app = new Vue({
el:"#app",
components:{
'my-cpn':{
template:'#myCpn',
data(){
return {
msg:"are you ready"
}
}
}
}
})
</script>
</body>
</html>
为什么是一个函数
- 首先如果不是一个函数,vue直接报错
- 其次,原因在于vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的话,组件在多次使用后相互影响
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>07-data为什么是函数</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<template id = "myCpn">
<div>
当前计数:{{counter}}
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
const obj = {
counter:0
};
Vue.component('cpn',{
template:'#myCpn',
data(){
return obj;
},
methods:{
increment(){
this.counter++;
},
decrement(){
this.counter--;
}
}
});
const app = new Vue({
el:"#app",
data:{
mes:"klk"
}
})
</script>
</body>
</html>
父子组件之间的通信
- 在上一小节中,我们提到了子组件是不能引用父组件或者vue实例的数据的
- 但是开发中,往往需要一些数据确实从上层传递给下层
- 比如一个页面中,我们从服务器请求里面获取很多数据
- 其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示的
- 这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)
- 如何进行父子组件之间的通信的?vue官方提到两种方案
-
通过props向子组件传递数据 -
通过事件向父组件发送消息
props基本使用
- 使用props来生命需要从父级接收到的数据
- props值两种方式
- 字符串数组,数组中的字符串就是传递时的名称
- 对象,对象可以设置传递时的类型,可以设置默认值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>08-父传子props使用</title>
</head>
<body>
<div id="app">
<cpn :cmessage="message" :cmovies="movies"></cpn>
</div>
<template id="myCpn">
<div>
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
<span>{{cmessage}}</span>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
Vue.component('cpn',{
template:"#myCpn",
data(){
return{
}
},
props:{
cmessage:{
type:String,
default:"默认消息",
required:true
},
cmovies:{
type:Array,
default(){
return []
}
}
}
});
const app = new Vue({
el:"#app",
data:{
message:"hello everybody",
movies:[
"大秦帝国","钢铁侠","水浒传"
]
}
})
</script>
</body>
</html>
props数据验证
Vue.component('my-com',{
props:{
propA:Number,
propB:[String,Number],
propC:{
type:String,
required:true
},
propD:{
type:Number,
default:100
},
propE:{
type:Object,
default(){
return{}
}
},
propF:{
validator(value){
return ['success','warning','danger'].indexOf(value) !== -1;
}
}
}
})
父传子props驼峰标识
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>09-父传子props驼峰标识</title>
</head>
<body>
<div id="app">
<cpn :c-info="info" :child-my-message="message"></cpn>
</div>
<template id="myCpn">
<div>
<h2> {{cInfo}}</h2>
<h2>{{childMyMessage}}</h2>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
Vue.component('cpn',{
template:"#myCpn",
props:{
cInfo:{
type:Object,
default(){
return {}
}
},
childMyMessage:{
type:String,
default:''
}
}
});
const app = new Vue({
el:"#app",
data:{
info:{
name:"geekmice",
age:98,
sex:"男"
},
message:"快来解决了"
}
})
</script>
</body>
</html>
子级向父级传递
- props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。
- 我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。
- 什么时候需要自定义事件呢?
- 当子组件需要向父组件传递数据时,就要用到自定义事件了。
- 我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。
- 自定义事件的流程:
- 在子组件中,通过$emit()来触发事件
- 在父组件中,通过v-on来监听子组件事件。
案例说明
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>10-子传父(自定义事件)</title>
</head>
<body>
<div id="app">
<cpn @item-click="cpnClick"></cpn>
</div>
<template id="myCpn">
<div>
<button v-for="item in categories" @click="btnClick(item)">
{{item.name}}
</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
const cpn = {
template: "#myCpn",
data() {
return {
categories: [
{ id: "aaa", name: "热门推荐" },
{ id: "bbb", name: "手机数码" },
{ id: "ccc", name: "家勇家电" },
{ id: "ddd", name: "电脑办公" },
],
};
},
methods: {
btnClick(item) {
this.$emit("item-click", item);
},
},
};
const app = new Vue({
el: "#app",
data: {
message: "kkk",
},
components: {
cpn
},
methods: {
cpnClick(item) {
console.log(item);
},
},
});
</script>
</body>
</html>
父子组件访问
- 有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问根组件
- 父组件访问子组件:
c
h
i
l
d
r
e
n
或
者
children或者
children或者refs
- 子组件访问父组件
- $children的访问
- this.$children是一个数组类型,它包括所有子组件对象
- 遍历去除所有子组件的message状态
父组件访问子组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn ref="abc"></cpn>
<cpn ref="aaa"></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>我是子组件</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
methods: {
btnClick() {
console.log(this.$children);
for (let c of this.$children) {
console.log(c.name);
c.showMessage();
}
console.log(typeof this.$refs);
console.log(this.$refs);
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是子组件的name'
}
},
methods: {
showMessage() {
console.log('showMessage');
}
}
},
}
})
</script>
</body>
</html>
效果演示
案例演示说明
子组件访问父组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是cpn组件</h2>
<ccpn></ccpn>
</div>
</template>
<template id="ccpn">
<div>
<h2>我是子组件</h2>
<button @click="btnClick">按钮</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></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() {
console.log(this.$parent);
console.log(this.$parent.name);
console.log(this.$root);
console.log(this.$root.message);
}
}
}
}
}
}
})
</script>
</body>
</html>
效果展示
案例效果说明
非父子组件访问
3、组件化高级
3.1、插槽slot
编译作用域
- 在真正学习插槽之前,我们需要先理解一个概念:编译作用域。
- 官方对于编译的作用域解析比较简单,我们自己来通过一个例子来理解这个概念:
我们来考虑下面的代码是否最终是可以渲染出来的: - 中,我们使用了isShow属性。
- isShow属性包含在组件中,也包含在Vue实例中。
- 答案:最终可以渲染出来,也就是使用的是Vue实例的属性。
- 为什么呢?
- 官方给出了一条准则:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
- 而我们在使用的时候,整个组件的使用过程是相当于在父组件中出现的。
- 那么他的作用域就是父组件,使用的属性也是属于父组件的属性。
因此,isShow使用的是Vue实例中的属性,而不是子组件的属性。
案例演示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn v-show="isShow"></cpn>
<cpn v-for="item in names"></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<p>我是内容, 哈哈哈</p>
<button v-show="isShow">按钮</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
isShow: true
},
components: {
cpn: {
template: '#cpn',
data() {
return {
isShow: false
}
}
},
}
})
</script>
</body>
</html>
效果展示
为什么使用插槽slot
如何封装这类组件slot
- 如何去封装这类的组件呢?
- 它们也很多区别,但是也有很多共性。
- 如果,我们每一个单独去封装一个组件,显然不合适:比如每个页面都返回,这部分内容我们就要重复去封装。
- 但是,如果我们封装成一个,好像也不合理:有些左侧是菜单,有些是返回,有些中间是搜索,有些是文字,等等。
- 如何封装合适呢?抽取共性,保留不同。
- 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
- 一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。
是搜索框,还是文字,还是菜单。由调用者自己来决定。 - 这就是为什么我们要学习组件中的插槽slot的原因。
slot基本使用
- 了解了为什么用slot,我们再来谈谈如何使用slot?
- 在子组件中,使用特殊的元素就可以为子组件开启一个插槽。
- 该插槽插入什么内容取决于父组件如何使用。
- 我们通过一个简单的例子,来给子组件定义一个插槽:
中的内容表示,如果没有在该组件中插入任何其他内容,就默认显示该内容 有了这个插槽后,父组件如何使用呢?
具名插槽
- 当子组件的功能复杂时,子组件的插槽可能并非是一个。
- 比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。
- 那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢?
- 这个时候,我们就需要给插槽起一个名字
- 如何使用具名插槽呢?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn><span slot="center">标题</span></cpn>
<cpn><button slot="left">返回</button></cpn>
</div>
<template id="cpn">
<div>
<slot name="left"><span>左边</span></slot>
<slot name="center"><span>中间</span></slot>
<slot name="right"><span>右边</span></slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn'
}
}
})
</script>
</body>
</html>
作用域插槽
是需要在插槽slot上绑定数据的。然后组件模板根据slot上绑定的数据进行操作。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn>
<template slot-scope="slot">
<span>{{slot.data.join(' - ')}}</span>
</template>
</cpn>
<cpn>
<template slot-scope="slot">
<span>{{slot.data.join(' * ')}}</span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="pLanguages">
<ul>
<li v-for="item in pLanguages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
pLanguages: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift']
}
}
}
}
})
</script>
</body>
</html>
3.2、动态组件
3.3、异步组件
4、组件生命周期
|