认识组件化
什么是组件化?
人面对复杂的问题时处理方法:
一个人的逻辑能力是有限的,如果面对一个非常复杂的问题,就不太可能一次性解决,通常我们会将问题进行拆解,将一个复杂的问题拆分成多个小问题,那么大问题就迎刃而解了。
组件化也有类似的思想:
如果我们将一个页面所有的逻辑全部放一起,那么处理起来会变得非常复杂,而且也不利于后续的扩展与维护。但如果将一个页面拆分成一个个小的功能块,每个功能块完成属于自己的功能,那么整个页面的管理和维护就非常容易了。
Vue组件化思想
- 组件化是Vue.js中的重要思想
- 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来完成我们的应用
- 任何应用都会被抽象成一棵组件树
- 组件化思想应用:
- 有了组件化思想,开发中要充分利用它,尽可能的将页面拆分成一个个小的可复用的组件,这样我们的代码更方便组织和管理,扩展性也更强
注册组件
注册组件的基本步骤
组件的使用分为三个步骤:
- 创建组件构造器 调用Vue.extend()方法创建组件构造器
- 注册组件 调用Vue.component()方法注册组件
- 使用组件 在Vue实例的作用范围内使用组件
<div id="app">
<!-- 3、使用组件 -->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
</div>
<script>
// 1、创建组件构造器对象
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容,11111</p>
<p>我是内容,22222</p>
</div>
`
});
// 2、注册组件
// 传两个参数:1、注册组件的标签名 2、组件构造器
Vue.component('my-cpn', cpnC)
let app = new Vue({
el: '#app',
data: {
msg: '你好'
}
})
</script>
注册组件步骤解析
- Vue.extend():
- 调用Vue.extend()创建的是一个组件构造器
- 通常在创建组件构造器时,传入template代表我们自定义的组件模板
- 该模板就是在使用到组件的地方,要显示的HTML代码
- 事实上,这种写法在Vue2.x的文档中几乎见不到了,它会直接使用语法糖
- Vue.component():
- 调用Vue.component()是将刚才的组件构造器注册成一个组件,并给它起一个组件的标签名称
- 需要传递两个参数:1、注册组件的标签名,2、组件构造器
- 组件必须挂载在某个Vue实例下,否则不会生效
组件的其他补充
全局组件和局部组件
当我们通过调用Vue.component()注册组件时,组件就是全局的,也就是该组件可以再任意的Vue实例中使用
如果注册的组件是挂载在某个实例中,那么这就是一个局部组件
<div id="app">
<cpn2></cpn2>
<cpn1></cpn1>
</div>
<div id="app2">
<!-- 局部组件时,app2就无法使用 -->
<cpn2></cpn2>
<cpn1></cpn1>
</div>
<script>
// 1、创建组件构造器
const cpnC1 = Vue.extend({
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容,11111</p>
</div>
`
});
const cpnC2 = Vue.extend({
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容,22222</p>
</div>
`
});
// 2、注册组件(全局组件,意味着可以在多个Vue的实例下面使用)
Vue.component('cpn1', cpnC1);
const app = new Vue({
el: '#app',
// 局部组件
components: {
cpn2: cpnC2
}
})
const app2 = new Vue({
el: '#app2'
})
</script>
父组件和子组件
前面的组件化思想中我们看到组件树的组件与组件之前存在层级关系,而其中非常重要的关系就是父子组件的关系
<div id="app">
<cpn2></cpn2>
<!-- 在vue实例或全局组件中没有注册该组件,无法使用 -->
<cpn1></cpn1>
</div>
<script>
// 1、创建第一个组件构造器(子组件)
let cpnC1 = Vue.extend({
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容,11111</p>
</div>
`
});
// 2、创建第二个组件构造器(父组件)
let cpnC2 = Vue.extend({
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容,22222</p>
<cpn1></cpn1>
</div>
`,
components: {
cpn1: cpnC1 // 当前只作用于父组件,外层无法使用
}
});
// root组件
let app = new Vue({
el: '#app',
data: {
msg: '你好'
},
components: {
cpn2: cpnC2
}
});
</script>
注册组件语法糖
上面注册组件的方式比较繁琐,Vue为了简化这个过程,提供了注册的语法糖,主要省去了调用Vue.extend()的步骤,可以直接使用一个对象来代替
<div id="app">
<cpn></cpn>
<cpn2></cpn2>
</div>
<script>
// 1、全局组件注册的语法糖
// 1、创建组件构造器
// const cpnC = Vue.extend();
// 2、注册全局组件
Vue.component("cpn", {
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容,11111</p>
</div>
`
})
// 2、注册局部组件的语法糖
let app = new Vue({
el: '#app',
data: {
msg: '你好'
},
components: {
'cpn2': {
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容,22222</p>
</div>
`
}
}
})
</script>
模板的分离写法
语法糖简化了Vue组件的注册过程,但template模块的写法还是比较麻烦,如果将其中的HTML分离出来写,然后再挂载到对应的组件上,结构就会变得非常清晰,Vue提供了两种方案定义HTML模块内容:
- 使用<script>标签
- 使用<template>标签
<div id="app">
<cpn></cpn>
</div>
<!-- script标签,注意:类型必须是text/x-template -->
<script type="text/x-template" id="cpn">
<div>
<h2>我是标题1</h2>
<p>我是内容,11111</p>
</div>
</script>
<script>
// 注册一个全局组件
Vue.component('cpn', {
template: '#cpn',
})
let app = new Vue({
el: '#app'
})
</script>
<div id="app">
<cpn></cpn>
</div>
<!-- template标签 -->
<template id="cpn">
<div>
<h2>我是标题2</h2>
<p>我是内容,22222</p>
</div>
</template>
<script>
// 注册一个全局组件
Vue.component('cpn', {
template: '#cpn',
})
let app = new Vue({
el: '#app'
})
</script>
组件数据存放
组件是一个单独封装的功能模块,有属于自己的HTML模板,也应该有属于自己的数据data(组件不能直接访问Vue实例中的data)。
组件对象有一个data属性(也有methods等属性),这个属性必须是一个函数,这个函数返回一个对象,对象内部保存这数据
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>{{title}}</h2>
<p>我是内容,22222</p>
</div>
</template>
<script>
Vue.component('cpn', {
template: '#cpn',
data() { // data必须是一个函数
return {
title: '我是标题'
}
}
})
let app = new Vue({
el: '#app',
data: {
msg: '你好'
}
})
</script>
为什么data在组件中必须是一个函数呢?
- 如果不是一个函数,Vue直接就会报错
- 原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象,组件在多次使用后会相互影响
父子组件通信
上面说到子组件不能直接访问父组件或Vue实例中的数据,但是在开发中,往往需要一些数据从上层传递到下层。
比如一个页面中,我们从服务器请求到了很多数据,但其中一部分需要下面的子组件进行展示,这个时候,并不会让子组件再发送请求,而是直接让父组件将数据传递给子组件。
如何进行父子组件间的通信?
- 通过props向子组件传递数据
- 通过事件向父组件发送消息
父级向子级传递
在组件中,使用props来声明需要从父级传递的数据
props的值有两种方式:
- 字符串数组,数组中的字符串就是传递时的名称
- 对象,对象可以设置传递时的类型,也可以设置默认值等
<div id="app">
<!-- 通过:cmsg="msg"将data中的数据传递给props -->
<cpn :cmsg="msg"></cpn>
</div>
<template id="cpn">
<div>
<!-- 将props中的数据显示在子组件中 -->
<h2>{{cmsg}}</h2>
</div>
</template>
<script>
let app = new Vue({
el: '#app',
data: {
msg: 'success'
},
components: {
cpn: {
template: '#cpn',
props: ['cmsg']
}
}
})
</script>
上面代码中,props选项使用的是一个数组,也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了,验证支持的数据类型:
String、Number、Boolean、Array、Object、Date、Function、Symbol、自定义构造函数
const cpn = {
template: '#cpn',
props: {
// 基础的类型检查(null 匹配任何类型)
propA: String,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true // 必须传的值
},
// 带有默认值的数字
propD: {
type: Number,
defalut:10
},
// 类型是对象或者数组时,默认值必须是一个函数
propE: {
type: Array,
defalut() {
return []
}
},
// 自定义验证函数
propF: {
validator(value) {
// 这个值必须匹配数组中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
},
// 自定义构造函数
propG: Person
}
}
function Person(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
子级向父级传递
子级向父级传递使用自定义事件来完成。
自定义事件的流程:
- 在子组件中,通过$emit()来触发事件
- 在父组件中,通过v-on来监听子组件事件
<div id="app">
<cpn :number1="num1" :number2="num2" @num1change="num1change" @num2change="num2change" />
</div>
<template id="cpn">
<div>
<h2>props: {{number1}} </h2>
<h2>data: {{dnumber1}} </h2>
<input type="text" v-model="dnumber1">
<h2>props: {{number2}} </h2>
<h2>data: {{dnumber2}} </h2>
<input type="text" v-model="dnumber2">
</div>
</template>
<script>
// 实例中定义两个变量num1、num2,传入子组件中
// 子组件修改变量,实现input值改变实例中的变量
// 修改num1,num2*100,修改num2,num1/100
let app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0
},
methods: {
num1change(value) {
this.num1 = parseFloat(value)
},
num2change(value) {
this.num2 = parseFloat(value)
}
},
components: {
cpn: {
template: '#cpn',
props: {
number1: Number,
number2: Number
},
data() {
return {
dnumber1: this.number1,
dnumber2: this.number2
}
},
watch: {
dnumber1(newValue) {
this.$emit('num1change', newValue)
this.dnumber2 = newValue * 100
},
dnumber2(newValue) {
this.$emit('num2change', newValue)
this.dnumber1 = newValue / 100
}
}
}
}
})
</script>
父子组件访问
父组件访问子组件:使用$children或$refs
子组件访问父组件:使用$parent
父访问子:$children
this.$children是一个数组类型,包含所有子组件对象
<div id="app">
<cpn></cpn>
<cpn></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>我是子组件</div>
</template>
<script>
let app = new Vue({
el: '#app',
data: {
msg: '你好'
},
methods: {
btnClick() {
console.log(this.$children);
for (const item of this.$children) {
console.log(item.name);
item.showMsg()
}
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是子组件的name'
}
},
methods: {
showMsg() {
console.log('showMsg');
}
}
},
}
})
</script>
父访问子:$refs
$children的缺陷:
- 通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值
- 当子组件过多,又需要拿到其中一个时,往往不能确认它的索引值,甚至还可能发生变化
- 想明确获取其中一个特定的组件,可以使用$refs
$refs的使用:
- $refs和ref指令通常是一起使用的
- 首先,通过ref给某一个子组件绑定一个特定的ID
- 然后,通过this.$refs.ID就可以访问到组件了
<div id="app">
<cpn ref="aaa"></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>我是子组件</div>
</template>
<script>
let app = new Vue({
el: '#app',
data: {
msg: '你好'
},
methods: {
btnClick() {
// $refs 对象类型 默认是一个空的对象,必须在组件上加ref="name"
console.log(this.$refs.aaa.name);
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是子组件的name'
}
}
},
}
})
</script>
子访问父:$parent、$root
子组件直接访问父组件,可以通过$parent。子组件访问根组件,使用$root
注意事项:
- 尽管在Vue开发中,允许通过$parent来访问父组件,但在真实的开发中尽量不要使用
- 子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了
- 如果将子组件放到另一个组件内,很可能该父组件没有对应的属性,会引起问题
- 更不要直接通过$parent来修改父组件的状态,那样会导致父组件中的状态变得飘忽不定,不利于维护和调试
<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>
let app = new Vue({
el: '#app',
data: {
msg: '你好'
},
components: {
cpn: {
template: '#cpn',
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.msg)
}
},
}
}
}
}
})
</script>
插槽slot
移动开发中,几乎每个页面都有导航栏,那我们就会将其封装成一个插件,但每个导航栏又有不同,这时就需要抽取共性,保留不同。将共性抽取到组件中,将不同暴露为插槽,使用时,只需要根据自己的需求,决定插槽中的内容。
slot基本使用
在子组件中,使用<slot>就可以为子组件开启一个插槽
该插槽显示内容取决于父组件中如何使用
<div id="app">
<cpn></cpn>
<cpn><span>这是span标签</span></cpn>
<cpn>
<i>这是i标签</i>
<div>这是div标签</div>
</cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<p>这是一段文字</p>
<slot><button>按钮</button></slot>
</div>
</template>
<script>
let app = new Vue({
el: '#app',
data: {
msg: '你好'
},
components: {
cpn: {
template: '#cpn'
}
}
})
</script>
具名插槽slot
当子组件功能复杂时,子组件的插槽可能有几个,这时需要给插槽起个名字,来区分插入的是哪一个,只要给slot元素起一个name属性即可。
<div id="app">
<cpn>
<button slot="left">返回</button>
<span slot="center">标题</span>
</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>
let app = new Vue({
el: '#app',
data: {
msg: '你好'
},
components: {
cpn: {
template: '#cpn'
}
}
})
</script>
编译作用域
父组件模板的所有东西都会再父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译
下面的例子中,使用<cpn v-show="isShow" ref="myCpn"></cpn>,它的作用域就是父组件,使用的属性也就是父组件的属性,isShow为true,子组件也就被渲染出来
<button v-show="isShow">按钮</button>,它的作用域是子组件,子组件的属性isShow为false,按钮就会被隐藏
<div id="app">
<cpn v-show="isShow" ref="myCpn"></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<p>我是内容</p>
<button v-show="isShow">按钮</button>
</div>
</template>
<script>
let app = new Vue({
el: '#app',
data: {
msg: '你好',
isShow: true
},
components: {
cpn: {
template: '#cpn',
data() {
return {
isShow: false
}
}
}
}
})
</script>
作用域插槽
父组件替换插槽的标签,但是内容由子组件来提供
通过<template slot-scope="slot">获取到slot属性
再通过slot.data获取传入的data
<div id="app">
<cpn></cpn>
<cpn>
<!-- 父组件替换插槽的标签,但是内容由子组件来提供 -->
<template slot-scope="slot">
<span>{{slot.data.join(" - ")}}</span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="languages">
<ul>
<li v-for="item in languages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script>
let app = new Vue({
el: '#app',
data: {
msg: '你好'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
languages: ['Java', 'JavaScript', 'PHP', 'C++', 'C#']
}
}
}
}
})
</script>