文章目录
组件
1.什么是组件化:
- 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的扩展和管理。
- 但如果,我们将一个页面拆分成一个个小的功能块,每个功能模块只需要完成属于自己独立的那一部分功能,那么后续管理和维护这个页面就会变得非常容易了。
- 我们将一个完整的页面分成很多个组件,每个组件都用于实现页面中的一个功能模块,而每一个组件又可以进行细分。例如下图:
2.Vue组件化思想:
- 组件化是vue中的重要思想
- 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
- 任何的应用都会被抽象成一颗组件树。
3.组件化思想的应用:
- 有了组件化的思想,我们在之后的开发中就要充分的利用它。
- 尽可能的将页面拆分成一个个小的、可复用的组件。
- 这样让我们的代码更加方便组织和管理,并且扩展性也更强。
所以,组件是Vue开发中,非常重要的一个篇章。
全局组件和局部组件
:
4.全局组件和局部组件:
1.全局组件:意味着可以在多个vue实例下面使用
。
<div id="app">
<cnp></cnp><!--使用组件-->
</div>
<script>
//1.创建组件构造器
const cnp = Vue.extend({
template:`
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
`
})
//2.注册组件(全局组件,意味着可以在多个vue实例下面使用!)
Vue.component('cnp',cnp)
</script>
2.局部组件:直接挂载在具体某个Vue实例下面创建,这样的话这个组件就只能在这个vue实例中使用!
(开发中用的比较多的就是局部组件)
<div id="app">
<cnp></cnp><!--使用组件-->
</div>
<script>
//1.创建组件构造器
const cnp1 = Vue.extend({
template:`
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
`
})
const app = new Vue({
el: "#app",
data: {
message: "你好!"
},
components: {
cpn: cnp1//cpn为组件的标签名,cnp1为组件构造器
}
})
</script>
5.父组件与子组件:
- 父组件中注册了子组件,所以形成了父子组件的关系
- 需要注意作用域的问题,父组件中注册的子组件只作用于当前的父组件,子组件想单独渲染必须在根组件中的components属性下进行注册!
<div id="app">
<cnp2></cnp2><!--使用组件-->
<cnp1></cnp1><!--子组件需要在根组件下的components属性下进行注册才可以渲染成功!-->
</div>
<script>
//1.创建组件构造器cnp1(子组件)
const cnp1 = Vue.extend({
template:`
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
`
})
//2.创建组件构造器cnp2(父组件)
const cnp2 = new Vue({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
<cnp1></cnp1>
`,
components: {
cnp1: cnp1//在cnp2里注册cnp1
}
})
//root组件(根组件)
const app = new Vue({
el: "#app",
data: {
message: "你好!"
},
components: {
cnp2: cnp2//第一个cnp2为组件的标签名,第二个cnp2为组件构造器
cnp1: cnp1//子组件需要在根组件下的components属性下进行注册才可以渲染成功,就是在当前根组件中进行注册!
}
})
</script>
6.全局组件和局部组件的语法糖:
概括:
- Vue为了简化这个过程,提供了注册的语法糖。
- 主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。
1.全局组件的语法糖:省去了组件构造器的步骤,直接放在注册组件(Vue.component())的后面(template),其实底层还是用了Vue.extend(),只是它底层帮我们做了这个步骤。
<script>
<!--注册组件-->
Vue.component("cnp1",{
template: `
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
<cnp1></cnp1>
`
})
</script>
2.局部组件的语法糖:将组件构造器参数直接用template那部分代码代替。
<script>
//root组件(根组件)
const app = new Vue({
el: "#app",
data: {
message: "你好!"
},
components: {
cnp2: {
template: `
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
`
}
}
})
</script>
7.模板的分离写法:
有两种写法:
1.第一种写法:通过script标签来写。
<script type="text/x-template" id="cpn">/第一种写法需要注意type的值!
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
</script>
<script>
//注册一个全局组件
Vue.component('cpn',{
template: '#cpn'
})
</script>
第二种写法:通过template标签来写。
<template id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>
</template>
<script>
Vue.component('cpn',{
template: '#cpn'
})
</script>
8.组件不能访问Vue实例数据:
- 组件是一个单独功能模块的封装:
- 这个模块有属于自己的HTML模板,也应该有属于自己的数据Data。
- 组件中的数据是保存在哪里呢?顶层的Vue实例当中吗?
- 组件不能直接访问Vue实例中的Data。
- 我们发现不能访问,而且即使可以访问,如果将所有的数据都放在Vue实例当中,Vue实例就会变得非常的臃肿!
结论:Vue组件应该有自己保存数据的地方。
9.组件数据的存放:
- 组件自己的数据存放在哪里呢?
- 组件对象也有一个data属性(也有methods属性,下面我们会用到!)
- 只是组件中的这个data属性必须是一个
函数
。 - 而且这个函数返回一个对象(就是data里的return),
对象内部保存着组件的数据
。
10.组件中的data为什么是函数(面试点):
- 如果data是函数的话,那么创建组件的时候都会去调用这个data函数,每一次调用data函数都会return一个新的对象,(因为data是一个函数,所以一旦被组件实例调用,返回的都是该组件实例所拥有的数据),然后每个组件实例都能获得自己想要的数据。
- 可反复调用,反复创建新对象
- 避免组件间数据冲突
11.父子组件的通信:
-
子组件是不能引用父组件或者Vue实例的数据的。
-
但是,在开发中,往往一些数据确实需要从上层传递到下层∶
- 比如在一个页面中,我们从服务器请求到了很多的数据。
- 其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。
- 这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)。
-
如何进行父子组件间的通信呢?Vue官方提到
- 通过
props
向子组件传递数据 - 通过
事件
向父组件发送消息 - 真实的开发中,Vue实例和子组件的通信和父组件和子组件的通信过程是一样的。
- 通过
父子组件的通信-父传子props
props的值有两种方式∶
- 方式一︰字符串数组,数组中的字符串就是传递时的名称。
- 方式二︰对象,对象可以设置传递时的类型,也可以设置默认值等。
<div id="app">
<cpn v-bind:cmessage="message" :cmovies="movies"></cpn>
</div>
<template id="cpn">
<div>
<ul>
<li v-for="items in cmovies">{{items}}</li>
</ul>
<p>{{cmovies}}</p>
</div>
</template>
<script>
//父传子props
const cpn = {
template: '#cpn',
// props: ['cmessage','cmovies'],//props的数组形式
props: { //props的对象形式
//1.类型限制
//cmovies: Array,
//cmessage: String,
//2.提供一些默认值以及必传值
cmessage: {
type: String,
default: '指定默认值',
required: true //用的时候必须传这个变量,否则报错!
},
cmovies: {
type: Array,
//类型是对象或者数组时,默认值必须是一个函数!
default(){
return[]
}
}
}
data(){
return{}
},
methods: {
}
}
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['篮球','吉他','音乐']
components:{
cpn
}
}
})
</script>
props数据验证:
在前面,我们的props选项是使用一个数组。
-
我们说过,除了数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了。
-
验证都支持哪些数据类型呢?
-
String
-
Number
-
Boolean
-
Array
-
Object
-
Date
-
Function
-
Symbol
-
props中的驼峰标识:
大写字母改成"-"加上小写字母,例如:cInfo------->c-info
<div id="app">
<cpn :c-info="info" :child-my-message="info"></cpn><!--这里的c-info和child-my-message就是用到了props的驼峰标识-->
</div>
<template id="cpn">
<h2>{{cInfo}}</h2>
<p>{{childMyMessage}}</p>
</template>
<script>
const cpn = {
template: '#cpn',
props: {
cInfo: {
type: Object,
default() {
return{}
}
},
childMyMessage: {
type: String,
default: ''
}
}
}
const app = new Vue({
el: '#app',
data: {
info: {
name: '机车',
age: 18,
height; 1.88
},
message: 'aaaaaaa'
}
})
</script>
父子组件的通信-子传父(自定义事件):
props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。
我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。
什么时候需要自定义事件呢?
-
当子组件需要向父组件传递数据时,就要用到自定义事件了。
-
我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。
自定义事件的流程∶︰
- 在子组件中,通过$emit()来触发事件。
- 在父组件中,通过v-on来监听子组件事件。
<!--父组件模板-->
<div id="app">
<cpn @item-click="cpnClick(item)"></cpn><!--cpnClick后面的参数item可以选择不传,可以省略掉,因为@item-click是自定义事件,不是浏览器的@click事件,所有在子组件cpn发射事件的时候(this.$emit('item-click',item))它会默认帮你把参数传过来!-->
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<button v-for="item in categories"
@click="btnClick(item)">
{{item.name}}
</button>
</div>
</template>
<script>
//子组件
const cpn = {
template: '#cpn',
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: '你好阿!'
},
components: {
cpn
},
methods: {
cpnClick(item){
console.log('cpnClick',item)
}
}
})
</script>
组件访问-父访问子-$children-$refs:
1.开发中使用$refs方式最多
<div id="app">
<button @click="btnClick">按钮</button>
<cpn ref="aaa"></cpn>
</div>
<template id="cpn">
<div>我是子组件</div>
</template>
<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()
}
console.log(this.$children[3].name)
//2.$refs ==> 对象类型,默认是一个空的对象,需要在组件标签中指定 ref='aaa'
console.log(this.$refs.aaa.name)
}
},
components: {
cpn: {
template: '#cpn',
data(){
return{
name: '我是子组件的name'
}
},
methods: {
showMessage(){
console.log('showMessage')
}
}
}
},
</script>
组件访问-子访问父-$parent-$root:
p a r e n t 开发中用的更少, parent开发中用的更少, parent开发中用的更少,root用的也不是很多
<!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="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn',
methods: {
},
data(){
return{
name: '我是子组件cpn的name'
}
},
components: {
ccpn : {
template: '#ccpn',
methods: {
btnClick(){
// 1.访问父组件$parent
// console.log(this.$parent)
// console.log(this.$parent.name)
// 2.访问根组件$root
console.log(this.$root);
console.log(this.$root.message);
}
}
}
}
},
}
})
</script>
</body>
</html>
12.组件化高级-slot- 插槽:
为什么使用插槽?
slot翻译为插槽:
- 在生活中很多地方都有插槽,电脑的USB插槽,插板当中的电源插槽。
- 插槽的目的是让我们原来的设备具备更多的扩展性。
- 比如电脑的USB我们可以插入U盘、硬盘、手机、音响、键盘、鼠标等等。
组件的插槽:
- 组件的插槽也是为了让我们封装的组件更加具有扩展性。
- 让使用者可以决定组件内部的一些内容到底展示什么。
例子:移动网站中的导航栏。
-
移动开发中,几乎每个页面都有导航栏。
-
导航栏我们必然会封装成一个插件,比如nav-bar组件。一旦有了这个组件,我们就可以在多个页面中复用了。
但是,每个页面的导航是一样的吗? No,我以京东M站为例
如何封装这类组件呢?slot
- 如何去封装这类的组件呢?
- 它们也很多区别,但是也有很多共性。
- 如果,我们每一个单独去封装一个组件,显然不合适︰比如每个页面都返回,这部分内容我们就要重复去封装。
- 但是,如果我们封装成一个,好像也不合理∶有些左侧是菜单,有些是返回,有些中间是搜索,有些是文字,等等。
- 如何封装合适呢?抽取共性,保留不同。
- 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
- 一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。
- 是搜索框,还是文字,还是菜单。由调用者自己来决定。
- 这就是为什么我们要学习组件中的插槽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></cpn>
<cpn><i>酷酷酷</i></cpn>
<cpn><p>帅刷机</p></cpn>
<cpn>
<div>机车</div><!--这三个会全部替换-->
<i>篮球</i>
<p>吉他</p>
</cpn>
</div>
<template id="cpn">
<div>
<h2>我是组件</h2>
<p>我是组件,哈哈哈</p>
<slot><button>按钮</button></slot>
<!-- <slot></slot>-->
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn',
}
}
})
</script>
</body>
</html>
slot-具名插槽的使用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn><span slot="left">标题</span></cpn>
<cpn><button slot="center">返回</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="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn: {
template: '#cpn',
}
}
})
</script>
</body>
</html>
编译作用域的概念:
在哪个组件模板里调用,就用哪个组件实例的值
-
在真正学习插槽之前,我们需要先理解一个概念∶编译作用域。
-
官方对于编译的作用域解析比较简单,我们自己来通过一个例子来理解这个概念∶
-
我们来考虑下面的代码是否最终是可以渲染出来的︰
<my-cpn v-show="isShow"></my-cpn>
中,我们使用了isShow属性。- isShow属性包含在组件中,也包含在Vue实例中。
-
答案︰最终可以渲染出来,也就是使用的是Vue实例的属性。
-
为什么呢?
- 官方给出了—条准则∶父姐件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
- 而我们在使用
<my-cpn v-show="isShow"></my-cpn>
的时候,整个组件的使用过程是相当于在父组件中出现的。 - 那么他的作用域就是父组件,使用的属性也是属于父组件的属性。
- 因此,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>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<h1>我是子组件</h1>
<p>我是内容,哈哈哈哈哈</p>
</div>
</template>
<script src="../js/vue.js"></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一个比较难理解的点,而且官方文档说的又有点不清晰。
这里,我们用一句话对其做一个总结,然后我们在后续的案例中来体会∶
- 父组件替换插槽的标签,但是内容由子组件来提供。
我们先提一个需求:
- 子组件中包括一组数据,比如: pLanguages: [‘JavaScript’,‘Python’, ‘Swift’,‘Go’, ‘C++’]
- 需要在多个界面进行展示︰
- 某些界面是以水平方向一—展示的,
- 某些界面是以列表形式展示的,
- 某些界面直接展示一个数组
- 内容在子组件,希望父组件告诉我们如何展示,怎么办呢?
- 利用slot作用域插槽就可以了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--父组件模板-->
<div id="app">
<cpn></cpn>
<!--目的是获取子组件中的pLanguages-->
<cpn>
<template slot-scope="slot"><!--这里的slot-scope-->
<!-- <span v-for="item in slot.data">{{item}} - </span>-->
<span>{{slot.data.join(' - ')}}</span><!--这里的slot.data-->
</template>
</cpn>
<cpn>
<template slot-scope="slot">
<!-- <span v-for="item in slot.data">{{item}} * </span>-->
<span>{{slot.data.join(' * ')}}</span>
</template>
</cpn>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<slot :data="pLanguages"><!--这里的:data="pLanguages"-->
<ul>
<li v-for="item in pLanguages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
<!-- 父组件-->
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
//子组件
cpn: {
template: '#cpn',
data(){
return{
pLanguages: ['javaScript','c++','java','c','python','Go']
}
}
}
}
})
</script>
</body>
</html>