文章目录
组件化的实现和使用步骤
什么是组件化?
-
人面对复杂问题的处理方式:
- 任何一个人处理信息的逻辑能力都是有限的。
- 所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆内容。
- 但是,我们人有一种天生的能力,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现问题也会迎刃而解。
-
组件化也是类似的思想:
- 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
- 但如果,我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么,之后整个页面的管理和维护就会变得非常容易了。
Vue的组件化思想
- 组件化是Vue.js中的重要思想
- 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
- 任何的应用都会被抽象成一颗组件数。
-
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
-
组件化思想的应用:
- 有了组件化的思想,我们在之后的开发中就要充分的利用它。
- 尽可能的将页面拆分成一个个小的、可复用的组件。
- 这样让我们的代码更加方便组织和管理,并且扩展性也很强。
注册组件的基本步骤
- 组件的使用分成三个步骤:
- 调用
Vue.extend()
方法创建组件构造器。 - 调用
Vue.component()
方法注册组件。 - 在Vue实例的作用范围内使用组件。
- 调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>01-组件化的基本使用</title>
</head>
<body>
<div id="app">
<!-- 3.使用组件 -->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpnConstructor = Vue.extend({
template:
`
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈哈</p>
<p>我是内容,嘿嘿嘿嘿嘿</p>
</div>
`
});
// 2.注册组件
Vue.component('my-cpn', cpnConstructor);
const app = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>
注册组件步骤解析
- 这里的步骤都代表什么含义呢?
Vue.extend()
:- 调用
Vue.extend()
创建的是一个组件的构造器。 - 通常在创建组件构造器时,传入template代表我们自定义组件的模板。
- 调用
Vue.component()
:- 调用
Vue.compoment()
是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。 - 所需要传递两个参数:1、注册组件的标签名。2、组件构造器。
- 调用
- 组件必须挂载在某个Vue实例下,否则不会生效,如图:
全局组件和局部组件
首先,我们上面写的组件就是全局组件,可以在不同的Vue实例中使用,下面来说一下局部组件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>01-组件化的基本使用</title>
</head>
<body>
<div id="app">
<!-- 3.使用组件 -->
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<div id="app2">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpnConstructor = Vue.extend({
template:
`
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈哈</p>
<p>我是内容,嘿嘿嘿嘿嘿</p>
</div>
`
});
// 2.注册组件(全局组件,意味着可以在多个Vue的实例下面使用)
Vue.component('my-cpn', cpnConstructor);
const app = new Vue({
el: '#app',
data: {
},
components: {
// my-cpn使用组件时的标签名
cpn: cpnConstructor,
}
});
const app2 = new Vue({
el: '#app2',
data: {
}
});
</script>
</body>
</html>
父组件和子组件
- 在前面我们看到了组件树:
- 组件和组件之间存在层级关系。
- 而其中一种非常重要的关系就是父子组件的关系。
- 父子组件的错误用法:以子标签的形式在Vue实例中使用。
- 因为当子组件注册到父组件的components时,Vue会编译好父组件的模块。
- 该模块的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件)
看如下案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>03-父组件和子组件</title>
</head>
<body>
<div id="app">
<cpn2></cpn2>
</div>
<script src="../js/vue.js"></script>
<script>
// 创建组件构造函数(子组件)
const cpnConstructor1 = Vue.extend({
template:`
<div>
<h2>我是子组件标题</h2>
<p>子组件子组件子组件</p>
</div>
`
});
// 创建组件构造函数(父组件)
const cpnConstructor2 = Vue.extend({
template:`
<div>
<h2>我是父组件标题</h2>
<p>父组件父组件父组件</p>
<cpn1></cpn1>
</div>
`,
components: {
cpn1: cpnConstructor1,
}
});
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn2: cpnConstructor2,
}
});
</script>
</body>
</html>
我们在父组件的模板中注册并使用而子组件,并且在vue挂载中注册了父组件,所以可以使用父组件,但是没有在vue挂载中注册子组件,所以无法使用子组件。
语法糖注册组件
语法糖注册全局组件
Vue.component('my-cpn', {
template: `
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈哈</p>
<p>我是内容,嘿嘿嘿嘿嘿</p>
</div>
`
});
语法糖注册局部组件
const app = new Vue({
el: '#app',
data: {
},
components: {
'cpn2': {
template:`
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈哈</p>
<p>我是内容,嘿嘿嘿嘿嘿</p>
</div>
`
}
}
});
组件模板的分离
- 刚才,我们通过语法糖简化了Vue组件的注册过程,另外还有一个地方的写法比较麻烦,就是template模块写法。
- 如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰。
- Vue提供两种方案来定义HTML模板内容:
- 使用
<script>
标签 - 使用
<template>
标签
- 使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>05-组件模板的分离写法</title>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
<!-- 1.使用script标签,注意type="text/x-template" -->
<!-- <script type="text/x-template" id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈</p>
</div>
</script> -->
<!-- 2.使用template标签 -->
<template id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈</p>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 注册一个全局组件
Vue.component('cpn', {
template: '#cpn'
});
const app = new Vue({
el: '#app',
data: {
},
components: {
}
});
</script>
</body>
</html>
组件可以访问Vue实例数据么?
- 组件是一个单独功能模块的封装:
- 这个模块由属于自己的HTML模板,也应该有属于自己的数据data。
- 组件中的数据时保存在哪里的呢?顶层的Vue实例中么?
- 我们经过测试,组件中不能直接访问Vue实例中的data。
- 而且即使可以访问,如果所有数据都放在Vue实例中,Vue实例就会变得非常臃肿。
- 结论:Vue组件应该有自己保存数据的地方。
组件数据的存放
- 组件自己的数据存放在哪里?
- 组件对象也有一个data属性(也可以有methods等属性,下面我们有用到)
- 只是这个data属性必须是一个函数
- 而且这个函数返回一个对象,对象内部保存着数据
父子组件的通信
-
在前面将父子组件的时候,我们提到子组件是不能引用父组件或者Vue实例的数据的。
-
但是在开发中,往往一些数据确实需要从上层传递到下层。
- 比如在一个页面中,我们从服务器请求到了很多的数据。
- 其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。
- 这个时候,并不会让子组件再一次发送一个网站请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)。
-
如何进行父子组件间的通信呢?Vue官方提到:
- 通过props向子组件传递
- 通过事件向父组件发送消息
-
下面的代码中,我们直接将Vue实例当做父组件,并且其中包含子组件来简化代码。
-
在真实开发中,Vue实例和子组件的通信与父组件和子组件的通信过程是一样的。
props基本用法(父传子)
-
在组件中,使用选项props来声明需要从父级接收到的数据。
-
props的值有两种方式:
- 方式一:字符串数组,数组中的字符串就是传递时的名称。
- 方式二:对象,对象可以设置传递时的类型,也可以设置默认值。
-
先看一个简单的props传递案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>08-组件通信-父组件向子组件传递数据</title>
</head>
<body>
<div id="app">
<cpn :cmovies="movies" :cmessage="message"></cpn>
</div>
<template id="cpn">
<div>
<h2>{
{
cmessage}}</h2>
<p>{
{
cmovies}}</p>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 父传子:props
const cpn = {
template: '#cpn',
data() {
return {
}
},
props: ['cmovies', 'cmessage'],
methods: {
},
}
const app = new Vue({
el: '#app',
data: {
message: '我是阿牛',
movies: ['海王', '海贼王', '海尔兄弟'],
},
components: {
cpn
}
});
</script>
</body>
</html>
- 在前面,我们的props选项是使用一个数组。
- 我们说过,除了数组之外,我们也可以使用对象,当需要对props进行类型验证时,就需要对象写法了。
- 验证都支持哪些数据类型呢?
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
- 当我们又自定义构造函数时,验证也支持自定义的类型。
props: {
cmovies: {
type: Array,
default() {
return []
},
},
cmessage: {
type: String,
default: '12345',
}
}
自定义事件(子传父)
- props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。
- 我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。
- 什么时候需要自定义事件呢?
- 当子组件需要向父组件传递参数时,我们就要用到自定义事件了。
- 我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。
- 自定义事件流程:
- 在子组件中,通过
$emit()
来触发事件。 - 在父组件中,通过v-on来监听子组件。
- 在子组件中,通过
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>09-组件通信-子传父(自定义事件)</title>
</head>
<body>
<!-- 父组件模板 -->
<div id="app">
<cpn @item-click="cpnClick"></cpn>
</div>
<!-- 子组件模板 -->
<template id="cpn">
<div>
<button v-for="item in categroies"
@click="btnClick(item)">{
{
item.name}}</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 子组件
const cpn = {
template: '#cpn',
data() {
return {
categroies: [
{
id: 'aaa', name: '热门推荐'},
{
id: 'aaa', name: '手机数码'},
{
id: 'aaa', name: '家用家电'},
{
id: 'aaa', name: '电脑办公'},
],
}
},
methods: {
btnClick(item) {
// 发射事件
this.$emit('item-click', item);
}
}
}
// 父组件
const app = new Vue({
el: '#app',
data: {
message: '我是阿牛',
},
components: {
cpn
},
methods: {
cpnClick(item) {
console.log(item.name);
}
}
});
</script>
</body>
</html>
这样我们点击按钮,就会触发子组件传递给父组件的item-click
事件,父组件执行方法cpnClick,将从子组件一同传来的参数item传入cpnClick方法,并执行该方法:
父子组件传值案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>10-组件通信-父子组件传递</title>
</head>
<body>
<!-- 父组件模板 -->
<div id="app">
<cpn :number1="num1"
:number2="num2"
@num1change="num1change"
@num2change="num2change"
></cpn>
</div>
<!-- 子组件模板 -->
<template id="cpn">
<div>
<h2>props:{
{number1}}</h2>
<h2>data:{
{dnumber1}}</h2>
<!-- <input type="text" v-model="dnumber1"> -->
<input type="text" :value="dnumber1" @input="num1Input">
<h2>props:{
{number2}}</h2>
<h2>data:{
{dnumber2}}</h2>
<input type="text" :value="dnumber2" @input="num2Input">
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 子组件
const cpn = {
template: '#cpn',
props: {
number1: Number,
number2: Number,
},
data() {
return {
dnumber1: this.number1,
dnumber2: this.number2,
}
},
methods: {
num1Input(event) {
// 1. 将input中的value赋值到dnumber中
this.dnumber1 = event.target.value;
// 2. 为了让父组件可以修改值,发出一个事件
this.$emit('num1change', this.dnumber1);
// 3. 同时修改dnumber2的值
this.dnumber2 = this.dnumber1 * 100;
this.$emit('num2change', this.dnumber2);
},
num2Input(event) {
// 1.将input中的value赋值到dnumber2
this.dnumber2 = event.target.value;
// 2. 为了让父组件可以修改值,发出一个事件
this.$emit('num2change', this.dnumber2);
// 3. 同时修改dnumber1的值
this.dnumber1 = this.dnumber2 / 100;
this.$emit('num1change', this.dnumber1);
}
}
}
// 父组件
const app = new Vue({
el: '#app',
data: {
num1: 0,
num2: 1
},
components: {
cpn
},
methods: {
num1change(value) {
this.num1 = value * 1;
},
num2change(value) {
this.num2 = value * 1;
}
}
})
</script>
</body>
</html>
-
该案例结合了双向绑定,融汇贯通父子组件的传值,略显复杂。
-
达到一个效果:下面的值num2是上面的值num1的100倍。
父子组件的访问方式
- 有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。
- 父组件访问子组件:使用
$children
或$refs
。 - 子组件访问父组件:使用
$parent
- 父组件访问子组件:使用
$children的使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>11-组件访问-父访问子-children</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
data() {
return {
name: '我是子组件的name'
}
},
methods: {
showMessage() {
console.log('showMessage');
}
}
}
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn
},
methods: {
btnClick() {
console.log(this.$children);
for(let c of this.$children){
console.log(c.name);
c.showMessage();
}
}
}
});
</script>
</body>
</html>
$refs的使用
- 上面我们使用
$children
获得子组件,当时使用这个方法在实际开发中比较少。因为想要获取某个特定的组件,就需要知道该组件在其父组件中的位置,也就是需要知道获得的数组的索引下标。 - 而我们用的最多的是这个
$refs
。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>12-组件访问-父访问子-ref</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn ref="aaa"></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
data() {
return {
name: '我是子组件的name'
}
},
methods: {
showMessage() {
console.log('showMessage');
}
}
}
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn
},
methods: {
btnClick() {
console.log(this.$refs.aaa.name);
}
}
});
</script>
</body>
</html>
$refs
获取一个对象,包含所有设置了ref属性(ref=aaa)的子组件,然后通过$refs.aaa
就可以获得该子组件了。
$parent
- 通过
$parent
可以访问父组件,但是在实际开发中使用较少。
$root
- 通过
$root
可以访问根组件,也很少用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>13-组件访问-子访问父-parent</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>我是ccpn</h2>
<button @click="getParent">获得父组件</button>
<button @click="getRoot">获得根组件</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const ccpn = {
template: '#ccpn',
methods: {
getParent() {
console.log(this.$parent);
},
getRoot() {
console.log(this.$root);
}
}
}
const cpn = {
template: '#cpn',
components: {
ccpn
}
}
const app = new Vue({
el: '#app',
components: {
cpn
}
});
</script>
</body>
</html>
回顾
Vue笔记一——Vue安装与体验
Vue笔记二——Vue声明周期与模板语法
Vue笔记三——计算属性(computed)
Vue笔记四——事件监听的使用
Vue笔记五——条件判断与循环遍历
Vue笔记六——书籍购物车案例
Vue笔记七——v-model表单输入绑定详细介绍