组件化开发是前端所提倡的,当你掌握了基础内容之后,就可以对组件这一大块内容进行了解了,本章将向你介绍组件相关内容以及简单的组件案例。
本章主要内容:
一、什么是组件
二、如何使用组件
三、组件之间的通讯
组件的概念
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。
所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子。
如何理解组件
简单理解,组件其实就是一个独立的html,它的内部可能有各种结构、样式、逻辑,某些地方来说有些像iframe,他都是在页面中引入之后展现另一个页面的内容,但实际上它与iframe又完全不同,iframe是一个独立封闭的内容,而组件既是一个独立的内容,还是一个受引入页面控制的内容。
为什么要使用组件
举个简单的列子,最近我的项目中有一个日历模块,有多个页面都日历,而每个页面的日历都存在一些差别,如果是不使用组件,我要完成这个项目,做到各个页面的日历大体一致,而部分地方存在差异,我可能就需要写几套日历代码了。
而使用组件呢?
一套代码,一个标签,然后分别在不同地方引用,根据不同的需求进行差异控制。
<calendar></calendar>
我可以通过给calendar传递值来实现在本页面对日历进行控制,让它满足我这个页面的某些单独需求。
有人会问,你calendar标签是什么鬼?前面有这么一句话,组件是自定义元素。calendar就是我自定义的元素。它就是一个组件。所以在项目中,你会发现有各种五花八门的标签名,他们就是一个个组件。
如何创建一个组件
我们把创建一个组件称为注册组件,如果你把组件理解成为变量,那么注册组件你就可以理解为声明变量。我们通过Vue.component来注册一个全局组件。
Vue.component(tagName, options)
tagName就是你组件的名称,也是你引入时候使用的自定义元素名称。
Vue.component('my-component', {
// 选项
})
notice:对于自定义标签的命名 Vue.js 不强制遵循 W3C 规则 (小写,并且包含一个短杠),尽管这被认为是最佳实践。
当你注册好组件之后,你就可以使用你的自定义元素my-component在其他实例中使用了。但是有一点,你注册组件需要在你创建实例之前进行。
<div id="example">
<my-component></my-component>
</div>
// 注册
Vue.component('my-component', {
template: '<div>this is my demo</div>'
})
// 创建根实例
new Vue({
el: '#example'
})
当代码执行之后,你的html代码将会被渲染为:
<div id="example">
<div>this is my demo</div>
</div>
前面说把组件比作变量,组件也存在局部组件与全局组件,前面为大家展示的是全局组件,接下来为大家介绍局部组件。
当你的组件不是全局都需要的时候,你就可以将其注册为局部组件。通过某个实例或者其他组件进行注册,你所注册的组件的作用范围就只存在与这个实例或者组件的作用域。
var Child = {
template: '<div>this is my demo</div>'
}
new Vue({
// ...
components: {
// <my-component> 将只在父组件模板中可用
'my-component': Child
}
})
关于组件使用的限制
当使用 DOM 作为模板时 (例如,使用 el 选项来把 Vue 实例挂载到一个已有内容的元素上),你会受到 HTML 本身的一些限制,
因为 Vue 只有在浏览器解析、规范化模板之后才能获取其内容。
尤其要注意,像 <ul>、<ol>、<table>、<select> 这样的元素里允许包含的元素有限制,
而另一些像 <option> 这样的元素只能出现在某些特定元素的内部。
在自定义组件中使用这些受限制的元素时会导致一些问题,例如:
<table>
<my-row>...</my-row>
</table>
自定义组件 <my-row> 会被当作无效的内容,因此会导致错误的渲染结果。变通的方案是使用特殊的 is 特性:
<table>
<tr is="my-row"></tr>
</table>
应当注意,如果使用来自以下来源之一的字符串模板,则没有这些限制:
<script type="text/x-template">
JavaScript 内联模板字符串
.vue 组件
因此,请尽可能使用字符串模板。
notice:构造 Vue 实例时传入的各种选项大多数都可以在组件里使用。
只有一个例外:data 必须是函数。
之前说过组件也是独立的,所以他也可以嵌套其他组件,所以你也可以在组件中注册组件。
组件之间的嵌套使用
组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。然而,通过一个良好定义的接口来尽可能将父子组件解耦也是很重要的。这保证了每个组件的代码可以在相对隔离的环境中书写和理解,从而提高了其可维护性和复用性。
每个组件的作用域都是独立的,所以在组件嵌套使用的时候子组件不能直接使用父组件中的数据。我们要如何在VUE中实现组件之间进行数据传递呢?
答案是Prop。
Prop是vue的一个属性,他的作用就是由父组件向子组件传递数据。
在 Vue 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。
前面说过推荐使用-命名法,为什么要使用-,而不是驼峰命名法?
HTML 特性是不区分大小写的。所以,当使用的不是字符串模板时,camelCase (驼峰式命名) 的 prop 需要转换为相对应的 kebab-case (短横线分隔式命名)
Prop的使用
父组件中声明prop,然后添加一个message
Vue.component('child', {
// 声明 props
props: ['message'],
// 就像 data 一样,prop 也可以在模板中使用
// 同样也可以在 vm 实例中通过 this.message 来使用
template: '<span>{{ message }}</span>'
})
然后直接传入值就可以在子组件中使用message
<child message="hello!"></child>
通过这样的方法我们可以将某些内容传递给子组件,但是有的时候需要在父元素的某些数据发生变化的时候子组件中的数据也要自动变化,这个时候应该如何做呢?
在之前的课程中我们有介绍过v-bind,在这里我们也可以通过v-bind将数据绑定到组件上,这样父组件中的参数发生变化的时候子组件中的数据就会自动变化。
//:是v-bind的缩写
<child :my-message="message"></child>
如果我要传递的是个对象怎么办呢?
使用上面的方法直接传递一个对象message可以这样做吗?
不,我们不这样做。
我们通过v-bind来完成对象的传递。
有人会问v-bind不就是上面写的吗?
让我们来看看下面的代码你就清楚了。
父组件中有个对象userInfo
userInfo: {
name: 'lzh',
age: '24'
}
我们通过v-bind传递给子组件
<my-demo v-bind="userInfo"></my-demo>
注意仔细看,前一个中是
v-bind:xxxx="XXXXXX"
后面这个是
v-bind="XXXXXX"
两者是不同的,后者你可以理解为:
<my-demo :name="lzh" :age="24"></my-demo>
这里有一个坑,日常容易出现的,就是使用:age传递的时候,这个时候传递的24并不是数值,而是字符串。
而我们在传递参数的时候是可以对数据类型进行定义的:
props: {
age: Number //这样传入的就只能是数值
}
也可以设置多种格式:
props: {
age: [String, Number] //这样可以传递字符串和数值
}
在设置数据类型的时候还可以同时设置默认值
props: {
age: {
type: Number,
default: 24 //这可以指定默认的值
}
}
其中type可以是
String
Number
Boolean
Function
Object
Array
Symbol
当父元素中的值通过v-bind绑定到子组件中之后,如果我们要使用这些数据参与逻辑处理应该怎么做呢?直接使用可以吗?答案是"no"。
如果你需要在子组件中对传递的数据进行其他处理,你需要在子组件的data中定义一个变量,然后将传递的值赋给你定义的变量:
props: ['date'],
data: function () {
return {
nowDate: this.date
}
}
上述内容就是如何通过prop 从父组件传值给子组件。
既然父元素可以通过prop 给子组件传值,那么子组件如何与父组件进行通讯呢?
o n ( e v e n t N a m e ) + on(eventName)+ on(eventName)+emit(eventName)实现通讯
Vue 的自定义事件:"是时候表演真正的技术了"
使用 v-on 绑定自定义事件
每个 Vue 实例都实现了事件接口,即:
使用 $on(eventName) 监听事件
使用 $emit(eventName) 触发事件
o n ( e v e n t N a m e ) + on(eventName)+ on(eventName)+emit(eventName)的用法:
在父组件中使用 o n ( e v e n t N a m e ) 监 听 事 件 , 然 后 在 子 组 件 中 使 用 on(eventName)监听事件,然后在子组件中使用 on(eventName)监听事件,然后在子组件中使用emit(eventName) 触发事件,这样就能实现子组件向父组件传值。
Vue 的事件系统与浏览器的 EventTarget API 有所不同。尽管它们的运行起来类似,但是 $on 和 $emit 并不是addEventListener 和 dispatchEvent 的别名。
另外,父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。
不能用 $on 侦听子组件释放的事件,而必须在模板里直接用 v-on 绑定,参见下面的例子。
下面是一个例子:
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
Vue.component('button-counter', {
template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
incrementCounter: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
console.log('第'+this.total+'次点击')
}
}
})
整体是如何工作的呢?
一个组件 button-counter
它绑定了一个click事件incrementCounter
当你点击子组件button-counter的时候
方法incrementCounter执行
this.counter += 1
然后通过$emit触发事件increment
this.$emit(‘increment’)
而父组件的increment实际上是事件incrementTotal
所以最终执行的就是
父组件中的incrementTotal
this.total += 1
并输出你的点击次数
整个流程就是这样,现在你也可以尝试自己写一个demo试试。
小结:组件的基础内容已经讲完了,为什么要用组件,为什么都提倡组件化的开发?大概是因为组件化开发具有内聚性和低耦合性。
什么是内聚性?
简单来说,我的功能都在这个组件中完成了。
什么是低耦合性?
我的组件在这里,谁都可以使用,但是不管谁使用都不会影响到其他人的使用,也不会影响到你的其他内容。
高复用性、宜维护性、内聚性、低耦合性
具备以上四个优势的VUE组件,如果你不会使用,如何算得上会VUE?