1.Vue组件介绍:
Vue组件 (Component) 是 Vue.js 最强大的功能之一。项目都是由组件构建起来的,组件也可以重用。所有的 Vue 组件同时也都是 Vue 的实例。
2.Vue单文件组件包含三个部分:
template | 视图部分,只能存在一个根元素。 |
script | 逻辑部分,其中 data 必须是函数。 |
style | 样式部分,建议使用 scoped 确保样式只在当前组件内生效。 |
模板如下:
<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> |