1.Vue组件介绍:
Vue组件 (Component) 是 Vue.js 最强大的功能之一。项目都是由组件构建起来的,组件也可以重用。所有的 Vue 组件同时也都是 Vue 的实例。
2.Vue单文件组件包含三个部分:
template
|
视图部分,
只能存在一个根元素。
|
script
|
逻辑
部分
,其中
data
必须是函数。
|
style
|
样式
部分
,建议使用
s
coped 确保样式只在当前组件内生效。
|
模板如下:
<template>
<div>
</div>
</template>
<script>
</script>
<style scoped>
</style>
|
3.全局注册组件(不常用):
1). 在 main.js 里全局注册组件:
// 注册
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
|
2). 在App.vue或者其它组件里面使用,例如在Hello.vue里使用:
<template>
<div>
<my-component></my-component>
</div>
</template>
<script>
</script>
<style scoped>
</style>
|
结果:
4.局部注册组件(常用):
全局注册组件会让程序可读性降低,我们可以通过某个 Vue 组件的实例选项 components 注册仅在其作用域中可用的组件(对 Parent 组件而言 Child 组件是其局部组件,对 App 而言 Parent 组件也是其局部组件):
1). 在 Child.vue 里面:
<template>
<div>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
name:"child",
data(){
return {
message: 'Hello Vue!'
}
}
}
</script>
<style scoped>
</style>
|
2). 在 Parent.vue 里面:
<template>
<div>
<Child/>
</div>
</template>
<script>
import Child from './Child'
export default {
name:"parent",
data(){
return {
}
},
components: {
Child
}
}
</script>
<style scoped>
</style>
|
3).在 App.vue 里面使用:
<template>
<div id="app">
<MyHelloWorld />
<Parent/>
</div>
</template>
<script>
import MyHelloWorld from './components/HelloWorld'
import Hello from './components/Hello'
import Parent from './components/Parent'
export default {
name: 'App',
components: {
MyHelloWorld,
Hello,
Parent
}
}
</script>
<style>
</style>
|
5.组件里面的 data 必须是函数:
如果data不是一个函数,那么所有使用该组件的实例将共享同一个data数据对象,如果data里面的数据改变,有可能引起其他组件数据也同时改变。所以为了消除这种影响,规定data必须是一个函数。
案例:
<template>
<div>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
name:"child",
data(){
return {
message: 'Hello Vue!'
}
}
}
</script>
<style scoped>
</style>
|
6.父子级组件之间的交互(通信):
组件实例的作用域是孤立的,为了解耦,组件之间不能直接进行数据的传递。在 Vue 中,父组件通过 props 给子组件下发数据,子组件通过事件给父组件发送消息。
1).父组件向子组件传递数据:props
①.案例:
a.在 Parent.vue 里面:
<template>
<div>
<!--前面是静态传递参数,后面是动态传递参数。参数名必须跟子组件props里面的名称一致-->
<Child staticMsg="I am static Message" :myMessage="todo"></Child>
</div>
</template>
<script>
import Child from './Child'
export default {
name:"parent",
data(){
return {
todo: {
text: 'Learn Vue',
isComplete: false
}
}
},
components: {
Child
}
}
</script>
<style scoped>
</style>
|
b.在 Child.vue 里面:
<template>
<div>
<p>{{ staticMsg }}</p>
<p>{{ myMessage }}</p>
</div>
</template>
<script>
export default {
name:"child",
props: ['staticMsg','myMessage'], //父组件上面的参数必须跟这里一致
data(){
return {
}
}
}
</script>
<style scoped>
</style>
|
注:Vue支持父组件将一个整体对象通过 props 传递给子组件。上面传递的 todo 即可说明。
②.不应该也不要在子组件内部直接改变 props 里面的值,如果想要改变,请使用下面方法:
a.方法一:定义一个局部变量,并用 props 的值初始化它:
<script>
export default {
name:"child",
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter //在子组件里面可以改变counter值,但是不要改变initialCounter的值
}
}
}
</script>
|
b.方法二:定义一个计算属性,处理 props 的值并返回:
<script>
export default {
name:"child",
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase() //只要size本身值不改变即可
}
}
}
</script>
|
注:对象或数组是引用类型,如果 props 是一个对象或数组,在子组件内部改变它会影响父组件的状态。这就是我们不应该在子组件里面改变父组件传过来值的原因。
③. 数据传递类型限制与验证(重要):
可以为组件的 props 指定验证规则。如果传入的数据不符合要求,Vue 会发出警告。要指定验证规则,需要用对象的形式来定义 props,而不能用字符串数组:
props: {
// 必须是数字 (`null` 指允许任何类型)
propA: Number,
// 可能是字符串或者是数字
propB: [String, Number],
// 必传且是字符串
propC: {
type: String,
required: true
},
// 数值且有默认值
propD: {
type: Number,
default: 100
},
// 数组/对象的默认值应当由一个工厂函数返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10
}
}
}
|
type 可以是下面原生构造器:
type验证类型
|
说明
|
String
|
字符串
|
Number
|
数字
|
Boolean
| 布尔 |
Function
|
函数
|
Object
|
对象
|
Array
|
数组
|
Symbol
|
符号
|
自定义构造器函数
|
使用 instanceof 检测
|
2).子组件向父组件传递数据:emit事件
①.案例:
a.在 Child.vue 里面:
<template>
<div>
<button v-on:click="incrementCounter">{{ counter }}</button>
</div>
</template>
<script>
export default {
name:"child",
data(){
return {
counter: 0
}
},
methods: {
incrementCounter: function () {
this.counter += 1
this.$emit('increment') //父组件在使用子组件时必须监听 increment 事件
}
}
}
</script>
<style scoped>
</style>
|
b.在 Parent.vue 里面:
<template>
<div>
<p>{{ total }}</p>
<!-- 其中 increment 名称必须与子组件this.$emit('increment')里面的名称一致-->
<Child v-on:increment="incrementTotal"></Child>
<Child v-on:increment="incrementTotal"></Child>
</div>
</template>
<script>
import Child from './Child'
export default {
name:"parent",
data(){
return {
total: 0
}
},
methods: {
incrementTotal: function () {
this.total += 1
}
},
components: {
Child
}
}
</script>
<style scoped>
</style>
|
②.给组件绑定原生事件:
<Child v-on:click.native="doTheThing"></Child > |
③.使用自定义事件的表单输入组件:
a.在 Child.vue 里面:
<template>
<div>
<input ref="input" v-bind:value="value" v-on:input="updateValue($event.target.value)">
</div>
</template>
<script>
export default {
name:"child",
props: ['value'],
methods: {
updateValue: function (value) {
if(value.indexOf('$') === -1){
value = "$" + value;
}
this.$emit('input', value)
}
}
}
</script>
<style scoped>
</style>
|
b.在 Parent.vue 里面:
<template>
<div>
<p>{{ price }}</p>
<Child v-model="price"></Child>
</div>
</template>
<script>
import Child from './Child'
export default {
name:"parent",
data(){
return {
price: 0
}
},
components: {
Child
}
}
</script>
<style scoped>
</style>
|
3).非父子组件的通信:
有时候,非父子关系的两个组件之间也需要通信。在简单的场景下,可以使用一个空的 Vue 实例作为事件总线:
①.新建一个空的 Vue 实例作为事件总线,在bus.js 文件中,内容如下:
import Vue from 'vue'
export default new Vue;
|
②.在组件 A.vue 里面触发事件:
<template>
<div>
<button v-on:click="incrementCounter">{{ counter }}</button>
</div>
</template>
<script>
import bus from './bus'
export default {
name:"component_a",
data(){
return {
counter: 0
}
},
methods: {
incrementCounter: function () {
this.counter += 1
bus.$emit('busincrement', this.counter) //其他组件如果想跟A通信,必须监听busincrement事件
}
}
}
</script>
<style scoped>
</style>
|
③.在组件 B.vue 中监听事件:
<template>
<div>
<p>{{ counter }}</p>
</div>
</template>
<script>
import bus from './bus'
export default {
name:"component_b",
data(){
return {
counter: 0
}
},
mounted() {
bus.$on('busincrement',function (value) {
this.counter = value
}.bind(this))
}
}
</script>
<style scoped>
</style>
|
④.在 App.vue 里面调用两个组件:
<template>
<div id="app">
<B/>
<A/>
</div>
</template>
<script>
import A from './components/A'
import B from './components/B'
export default {
name: 'App',
components: {
A,
B
}
}
</script>
<style>
</style>
|
7.插槽:
所谓插槽就是在父组件里面先占一个位置,如果子组件有插口,则将子组件插口里面的内容放到父组件的插槽里面;如果子组件没有插口,则使用父组件备用(默认)的内容。
最初在 <slot> 标签中的任何内容都被视为备用(默认)内容。备用(默认)内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用(默认)内容。
1).单个插槽:
①. 在子组件 Child.vue 模板里面:
<div> |
<div> |
<div> |
2).具名插槽:
<slot> 元素可以用一个特殊的特性 name 来进一步配置如何分发内容。多个插槽可以有不同的名字。具名插槽将匹配内容片段中有对应 slot 特性的元素。仍然可以有一个匿名插槽,它是默认插槽,作为找不到匹配的内容片段的备用插槽。如果没有默认插槽,这些找不到匹配的内容片段将被抛弃。
①. 在子组件 app-layout 模板中:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
|
②.父组件模板:
<app-layout>
<h1 slot="header">这里可能是一个页面标题</h1>
<p>主要内容的一个段落。</p>
<p>另一个主要段落。</p>
<p slot="footer">这里有一些联系信息</p>
</app-layout>
|
③.渲染结果为:
<div class="container">
<header>
<h1>这里可能是一个页面标题</h1>
</header>
<main>
<p>主要内容的一个段落。</p>
<p>另一个主要段落。</p>
</main>
<footer>
<p>这里有一些联系信息</p>
</footer>
</div>
|
3).作用域插槽:数据是子组件传给父组件
①. 在子组件 child 中,只需将数据传递到插槽,就像你将 props 传递给组件一样:
<div>
<slot text="hello from child"></slot>
</div>
|
②. 在父级中,具有特殊特性 slot-scope 的 <template> 元素必须存在,表示它是作用域插槽的模板。slot-scope 的值将被用作一个临时变量名,此变量接收从子组件传递过来的 props 对象:
<div class="parent">
<child>
<template slot-scope="props">
<span>hello from parent</span>
<span>{{ props.text }}</span>
</template>
</child>
</div>
|
③. 如果我们渲染上述模板,得到的输出会是:
<div class="parent">
<div class="child">
<span>hello from parent</span>
<span>hello from child</span>
</div>
</div>
|
注意:在2.5.0之前,必须使用到template身上。
8.动态组件:
1).动态组件案例:
①.分别创建 Home.vue,Posts.vue,Archive.vue 内容如下(适当修改 msg 内容和 name 名称):
<template>
<div>
{{ msg }}
</div>
</template>
<script>
export default {
name:"home",
data(){
return {
msg: "我是home组件"
}
}
}
</script>
<style scoped>
</style>
|
②.在
App.vue
里面使用三个组件:
<template>
<div id="app">
<button v-on:click="changeView">改变组件</button>
<component v-bind:is="currentView">
<!-- 组件在 vm.currentview 变化时改变! -->
</component>
</div>
</template>
<script>
import Home from './components/Home'
import Posts from './components/Posts'
import Archive from './components/Archive'
export default {
name: 'App',
data(){
return {
currentView: 'Home'
}
},
methods:{
changeView : function(){
if(this.currentView == 'Home'){
this.currentView ='Posts'
}else if(this.currentView == 'Posts'){
this.currentView ='Archive'
}else{
this.currentView ='Home'
}
}
},
components: {
Home,
Posts,
Archive
}
}
</script>
<style>
</style>
|
2).组件缓存:
keep-alive
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数:
<keep-alive>
<component :is="currentView">
<!-- 非活动组件将被缓存! -->
</component>
</keep-alive>
|
9.Vue 组件的 API 来自三部分:
API
| 作用 |
props
|
允许外部环境传递数据给组件,即父组件传递给子组件
|
事件
|
允许从组件内触发外部环境的副作用,即子组件传递给父组件
|
插槽
|
允许外部环境将额外的内容组合在组件中,即父组件传递给子组件
|
10.对低开销的静态组件使用 v-once:
尽管在 Vue 中渲染 HTML 很快,不过当组件中包含大量静态内容时,可以考虑使用 v-once 将渲染结果缓存起来,如下:
<template>
<div v-once>
<h1>因为我都是静态内容,而且不易变化,所有缓存起来!</h1>
</div>
</template>
<script>
export default {
name:"child",
data(){
return {
}
}
}
</script>
<style scoped>
</style>
|