Vue 自定义组件
组件化是 Vue.js 中的重要思想,它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。任何的应用都会被抽象成一颗组件树。
一. 组件使用的基本流程
组件的使用分成三个步骤:
- 创建组件构造器
- 注册组件
- 使用组件
创建组件构造器:
const cpn = Vue.extend({
template: `
<div>
<h2>标题</h2>
<p>内容 1</p>
<p>内容 2</p>
</div>
`
})
注册组件:
//第一个参数是组件的标签名,第二个参数是组件的构造器
Vue.component('cpn',cpn);
使用组件:
<cpn></cpn>
Vue.extend():
- 调用 Vue.extend() 创建的是一个组件构造器。
- 通常在创建组件构造器时,传入 template 代表我们自定义组件的模板。
- 该模板就是在使用到组件的地方,要显示的 HTML 代码。
- 事实上,这种写法在 Vue2.x 的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
Vue.component():
调用 Vue.component() 是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
所以需要传递两个参数:1.注册组件的标签名、2.组件构造器
二. 全局组件和局部组件
2.1 全局组件
当我们通过调用 Vue.component() 注册组件时,组件的注册是全局的。这意味着该组件可以在任意 Vue 实例下使用。我们下面创建了两个 Vue 实例,然后通过注册全局组件,在两个 Vue 实例所管理的 HTML 标签下都能够使用该组件:
<!--全局组件能够在多个 Vue 实例中使用-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app1">
<cpn></cpn>
</div>
<div id="app2">
<cpn></cpn>
</div>
</body>
<script src="./js/vue.js"></script>
<script>
const cpn = Vue.extend({
template: `
<div>
<h2>标题</h2>
<p>内容 1</p>
<p>内容 2</p>
</div>
`
})
//注册全局组件
Vue.component('cpn', cpn);
const app1 = new Vue({
"el": "#app1",
data: {}
});
const app2 = new Vue({
"el": "#app2",
data: {}
});
</script>
</body>
</html>
2.2 局部组件
如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件:
const app2 = new Vue({
"el": "#app2",
data: {},
//注册局部组件,局部组件只能在当前实例中使用
components: {
'cpn': cpn
}
});
三. 组件注册的语法糖
在上面介绍了组件注册的方式,可能会有些繁琐。在 Vue 官网上介绍的是简化的注册方式,主要是省去了调用 Vue.extend() 的步骤,可以直接使用一个对象来代替:
Vue.component('cpn', {
template: `
<div>
<h2>标题</h2>
<p>内容 1</p>
<p>内容 2</p>
</div>`
});
四. 组件模板的抽离
刚才,我们通过语法糖简化了 Vue 组件的注册过程,另外还有一个地方的写法比较麻烦,就是 template 模块写法.如果我们能将其中的 HTML 分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰。Vue 提供了两种方案来定义 HTML 模块内容:
4.1 使用 <script>
标签
4.2 使用 <template>
标签
五. data 必须是一个函数
一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。
Vue.component('cpn', {
template: `
<div>
<h2>{{title}}</h2>
<p>{{content1}}</p>
<p>{{content2}}</p>
</div>
`,
data() {
return {
title: "标题",
content1: "内容一",
content2: "内容二"
}
}
});
六. 组件之间的通信
- Parent->Child:通过 props 向子组件传递数据。
- Child->Parent:通过事件向父组件发送消息。
6.1 父传子
在开发中,往往一些数据确实需要从上层传递到下层。比如在一个页面中,我们从服务器请求到了很多的数据,其中一部分数据,并非是我们整个页面的大组 件来展示的,而是需要下面的子组件进行展示。这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件) 将数据传递给小组件(子组件)。
<!--全局组件能够在多个 Vue 实例中使用-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn :item="books[0]"></cpn>
<cpn :item="books[1]"></cpn>
</div>
</body>
<script src="./js/vue.js"></script>
<script>
//声明子组件
const cpn = {
template: `
<div>
<h2>{{item.title}}</h2>
<p>{{item.price}}</p>
</div>
`,
//通过 props 接收父组件传入的值
props: ['item']
}
const app = new Vue({
"el": "#app",
data: {
books: [
{
title: "《深入 Java 虚拟机》",
price: 75
},
{
title: "《Java 并发编程》",
price: 46
}
]
},
//注册局部组件
components: {
//ES6 简略写法,等同于"cpn":cpn
cpn
}
})
</script>
</body>
</html>
6.1.1 props 驼峰命名避坑
需要注意的是我们在使用 v-bind
指令给子组件绑定值时不能使用大写,因为 HTML 标签属性是不区分大小写的,换句话说类似于驼峰命名的格式也不能使用,不然子组件无法接受到父组件传入的值。例如:
//下面这个情况子组件接收不到值
<cpn :productList="books[0]"></cpn>
const cpn = {
template: `
<div>
<h2>{{productList.title}}</h2>
<p>{{productList.price}}</p>
</div>
`,
//通过 props 接收父组件传入的值
props: ['productList']
}
如果我们 props 需要使用驼峰进行语义区分,我们需要在使用 v-bind
绑定参数时使用 -
分割:
//使用“-”区分语义,然后 pros 使用驼峰即可
<cpn :product-list="books[0]"></cpn>
const cpn = {
template: `
<div>
<h2>{{productList.title}}</h2>
<p>{{productList.price}}</p>
</div>
`,
//通过 props 接收父组件传入的值
props: ['productList']
}
需要注意的是,上述问题只会在静态引入 Vue 框架时才会出现,如果是采用脚手架开发的话由于项目会有一个编译过程, vue-cli
帮我们解决了这个问题,所以这个问题就不存在了。
6.1.2 props 数据验证
在前面,我们的 props 选项是使用一个数组。我们说过,除了数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了。验证都支持哪些数据类型呢?
- String
- Number
- Boolean
- Array
- Object
- Date
- Funciton
- Symbol
当我们有自定义构造函数时,验证也支持自定义的类型。
数据验证的写法举例:
6.1.3 子组件中应该避免修改 props 中的值
在父组件给子组件传值时子组件会使用 props 创建一个属性去接收父组件传入的值,这个值我们在子组件中是不建议改变的,不然 Vue 会报出警告:
<div id="app">
<cpn :num1="num1" :num2="num2"></cpn>
</div>
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0
},
components: {
cpn: {
template: `
<div>
<h2>{{num1}}</h2>
<input type="text" v-model="num1">
<h2>{{num2}}</h2>
<input type="text" v-model="num2">
</div>
`,
props: [
'num1', 'num2'
]
}
}
如果我们真的有需求对父组件传入的值进行修改,我们可以在子组件中创建一个 data 属性,让其初始化为 props 接收的值,然后我们就可以修改 data 中属性的值了:
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0
},
components: {
cpn: {
template: `
<div>
<h2>{{dnum1}}</h2>
<input type="text" v-model="dnum1">
<h2>{{dnum2}}</h2>
<input type="text" v-model="dnum2">
</div>
`,
data() {
return {
dnum1: this.num1,
dnum2: this.num2
};
},
props: [
'num1', 'num2'
]
}
}
});
6.2 子传父
子组件向父组件传进行通信,需要子组件通过 this.emit
发射事件,然后父组件捕捉到事件,从而触发这个事件绑定函数。如果需要传递数据,则在发送数据时通过 this.emit
携带数据即可。
子传父通信流程:
<!--全局组件能够在多个 Vue 实例中使用-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn v-on:change-selective="cpnEvent"></cpn>
{{category<=0?'':category}}
</div>
</body>
<script src="./js/vue.js"></script>
<script>
const cpn = {
template: `
<select @change="changeSelective" v-model="category">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
`,
data() {
return {
category: null
}
},
methods: {
changeSelective() {
//发射事件,第一个参数是发射的事件名称,第二个参数是事件携带的数据
this.$emit('change-selective', this.category);
}
}
}
const app = new Vue({
"el": "#app",
data: {
category: "-1"
},
components: {
//ES6 简略写法,等同于"cpn":cpn
cpn
},
methods: {
cpnEvent(eventValue) {
alert(eventValue)
}
}
})
</script>
</body>
</html>
七.组件访问
有时候需要父组件访问子组件,子组件访问父组件,或者是子组件访问根组件。 在组件实例中,Vue 提供了相应的属性,包括 $parent
、 $children
、 $refs
和 $root
,这些属性都挂载在组件的 this 上。本文将详细介绍 Vue 组件实例间的直接访问
7.1 父组件访问子组件
7.1.1 $children
我们可以通过 $children
拿到子组件的对象,调用子组件的方法或者获取属性值。通过 console.log
打印 $children
我们发现它实际上是一个数组,数组中每一个元素就是当前组件的一个子组件:
假如饿哦们在子组件中定义了一个 showMessage
方法用于打印组件中 message
属性,现在我们可以通过 $children
获取子组件,然后调用它的 showMessage
方法:
<cpn></cpn>
<button @click="showChildMessage">调用子组件的 showMessage 方法</button>
const app = new Vue({
el: '#app',
methods:{
showChildMessage(){
this.$children[0].showMessage();
}
},
components: {
cpn: {
template: `
<div>
子组件:{{message}}
</div>
`,
data() {
return {
message: '子组件消息'
};
},
methods:{
showMessage(){
console.log(this.message);
}
}
}
}
});
7.1.2 $refs
由于我们使用 $children
时会返回的是子组件数组,而这个数组会随着 HTML 中组件的使用顺序去改变,所以在开发中通过下标写死会给后序的维护带来很多潜在的大坑(别人可能在你写的组件前加了一个新组件后,发现原来的功能不能用了)。
我们可以使用 $refs
来解决这个问题,我们只需要在使用组件时给组件指定一个 key:
<cpn ref="aaa"></cpn>
然后我们就可以通过 $refs.key
获取到指定的组件对象了:
<div id="#app">
<cpn ref="aaa"></cpn>
<button @click="showChildMessage">调用子组件的 showMessage 方法</button>
</div>
const app = new Vue({
el: '#app',
methods:{
showChildMessage(){
this.$refs.aaa.showMessage();
}
},
components: {
cpn: {
template: `
<div>
子组件:{{message}}
</div>
`,
data() {
return {
message: '子组件消息'
};
},
methods:{
showMessage(){
console.log(this.message);
}
}
}
}
});
7.2 子组件访问父组件
7.2.1 $parent
$parent
可以获取当前组件的父组件对象:
<div id="app">
父组件:{{message}}
<cpn></cpn>
</div>
const app = new Vue({
el: '#app',
methods:{
showMessage(){
console.info(this.message)
}
},
data:{
message: "父组件消息"
},
components: {
cpn: {
template: `
<div>
<button @click="showParentMessage">访问父组件</button>
</div>
`,
methods:{
showParentMessage(){
this.$parent.showMessage();
}
}
}
}
});
7.2.2 $root
$root
可以获取根组件,也就是 Vue 实例。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 对数组进行赋值 Vue 无法响应
下一篇: 谈谈自己对于 AOP 的了解
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论