首发日期:2019-01-26vue
【官方的话】组件系统是 Vue 的另外一个重要概念,由于它是一种抽象,容许咱们使用小型、独立和一般可复用的组件构建大型应用。仔细想一想,几乎任意类型的应用界面均可以抽象为一个组件树:webpack
小菜鸟的话:定义组件就好像定义了一堆“带名字”的模板,好比说可能会有叫作“顶部菜单栏”的组件,咱们能够屡次复用这个“顶部菜单栏”而省去了大量重复的代码。web
<body> <div id="app"> <my-template></my-template><!-- 利用组件名来使用定义的“模板” --> <my-template></my-template> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('my-template',{ // 第一个参数是组件名,第二个参数是模板的内容 template: '<div><span>个人模板</span></div>' }) var vm = new Vue({ el: '#app' }) </script>
代码效果:
vuex
组件注册就是“定义模板”,只有注册了的组件,Vue才可以了解怎么渲染。npm
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div id="app"> <ol> <!-- 使用组件 --> <todo-item></todo-item> <todo-item></todo-item> <todo-item></todo-item> </ol> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> //全局定义组件,第一个参数是组件名,template的值是组件的内容 Vue.component('todo-item', { template: '<li>这是个待办项</li>' }) // 实例化是必须的,要把使用组件的区域交给vue管理 var app = new Vue({ el: '#app', }) </script> </html>
全局注册每每是不够理想的。好比,若是你使用一个像 webpack 这样的构建系统,全局注册全部的组件意味着即使你已经再也不使用一个组件了,它仍然会被包含在你最终的构建结果中。这形成了用户下载的 JavaScript 的无谓的增长。数组
在这些状况下,你能够经过一个普通的 JavaScript 对象来定义组件:浏览器
<body> <div id="app"> <my-component></my-component><!-- 利用组件名来使用组件 --> <my-component></my-component> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var ComponentA = { // 定义一个组件 template: '<div><span>个人模板</span></div>' } var vm = new Vue({ el: '#app', components: { // 而后在实例中声明要使用这个组件,key是在这个实例中的组件名,value是组件 'my-component': ComponentA } }) </script>
上面的全局注册说了容许在组件中使用其余组件,但注意局部注册的组件要声明使用其余组件才可以嵌套其余组件。例如,若是你但愿 ComponentA 在 ComponentB 中可用,则你须要这样写:缓存
<body> <div id="app"> <my-component></my-component> <my-component-b></my-component-b> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var ComponentA = { // 1.定义一个组件 template: '<div><span>个人模板</span></div>' } var ComponentB = { // 2.定义一个组件 components: { //3.声明使用A组件 'my-component': ComponentA }, template: '<my-component></my-component>' // 4.须要在组件的template中写上另外一个组件 } var vm = new Vue({ el: '#app', components: { 'my-component': ComponentA, 'my-component-b': ComponentB } }) </script>
组件名可使用类my-component-name(kebab-case (短横线分隔命名))或MyComponentName的格式(PascalCase 首字母大写命名法),使用组件的时候能够<my-component-name>
或<MyComponentName>
,但在有些时候首字母大写命名法定义组件的是不行的,因此一般推荐使用<my-component-name>
【当你使用首字母大写命名法来定义组件的时候,不能直接在body中直接写组件名,而要求写在template中,以下例】。app
<body> <div id="app"> <my-component></my-component> <my-component-demo></my-component-demo> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('my-component',{ template: '<div><span>个人模板A</span></div>' }) Vue.component('my-component-demo',{ // 这个是用来测试第二种命名法定义组件的,首字母大写时要写在字符串模板中才能显示(否则显示不了) template: '<MyComponentB></MyComponentB>' }) Vue.component('MyComponentB',{ template: '<div><span>个人模板B</span></div>' }) var vm = new Vue({ el: '#app' }) </script>
每一个组件必须只有一个根元素!!
因此下面是不合法的:
若是你确实要有多个元素,那么要有一个根元素包裹它们:
组件也是一个实例,因此组件也能够定义咱们以前在根实例中定义的内容:data,methods,created,components等等。
但一个组件的 data 选项必须是一个函数,所以每一个实例能够维护一份被返回对象的独立的拷贝
在一些html元素中只容许某些元素的存在,例如tbody元素中要求有tr,而不能够有其余的元素(有的话会被提到外面)。下面是一个元素被提到外面的例子【而ul并无太严格,因此咱们在前面的todo-list的例子中可以演示成功】
下图能够看的出来div被提到table外面了:
这是为何呢?目前来讲,咱们在页面中实际上是先通过html渲染再通过vue渲染的(后面项目话后是总体渲染成功再展现的),当html渲染时,它就发现了tr里面有一个“非法元素”,因此它就把咱们自定义的组件提到了table外面。
解决方案:
使用tr元素,元素里面有属性is,is的值是咱们要使用的组件名
<body> <div id="app"> <table> <tr is='mtr'></tr> </table> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('mtr', { template: `<tr><td>123</td></tr>` // 这个成功的检测要使用检测元素来看 }); var app = new Vue({ el: '#app' }) </script>
但不会在一下状况中出错:
<script type="text/x-template">
在上面定义的组件中使用的数据都是固定的数据,一般咱们都但愿模板能根据咱们传入的数据来显示。
(子组件的意思是当前组件的直接子组件,在目前的单个html文件为例时,你能够仅认为是当前页面的非嵌套组件。后面讲到多个组件的合做时因为多个组件之间的嵌套,就造成了组件的父子、祖孙、兄弟关系)
要给子组件传递数据主要有两个步骤
演示代码以下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div id="app"> <ol> <!-- 2.给数据赋值 --> <todo-item todo="第一个值"></todo-item> <todo-item todo="第二个值"></todo-item> <todo-item :todo="msg"></todo-item> <!-- 传入的数据能够是父组件实例中定义的数据。注意要用:来绑定,否则不会识别成实例数据,而是一个字符串 --> </ol> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('todo-item', { props: ['todo'], // 1.使用props来定义这个组件支持传的参数 template: '<li>{{ todo }}</li>' // 3.这里演示一下在组件中使用这个传入的参数 }) var app = new Vue({ el: '#app', data: { msg: 'hello world' } }) </script> </html>
代码效果:很明显的,咱们的值成功传给子组件了。
$emit()
能够有多个参数,第一个参数是触发的事件的名称,后面的参数都是随着这个事件向外抛出的参数。演示代码以下:
<body> <div id="app" > <my-component-c v-on:change-color="doSomething"></my-component-c> <!-- 2.使用组件的时候监听一下事件,这里将会调用父组件的函数来处理事件 --> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('my-component-c', { template: ` <button @click="$emit('change-color','hello')">改变颜色</button> ` // 1.定义组件,在组件内定义一个能触发click事件的按钮,onclick事件发生时将会调用emit来向外抛出事件【这里触发的事件不能使用驼峰命名法。推荐使用短横线命名法】 // 【第一个参数是自定义事件名称,第二个和后续的参数能够用于向组件抛出值,这些值会做为事件响应函数的参数】 }) var app = new Vue({ el: '#app', data: { msg: 'hello world' }, methods: { // 3.将会调用下面的函数来处理事件 doSomething: function(value){ // 这里的value是从子组件中抛出的。 console.log(value) // 打印一下 } } }) </script>
【小tips:上面有多重字符串的使用,普通的双引号和单引号已经不足以嵌套使用了,在外层可使用反引号` ` `【esc下面那个键】来包裹,它也能够达到字符串包裹的效果,特别的是它支持多行字符串。】
祖孙组件传数据、兄弟组件传数据都属于非父子组件之间的传值。
使用bus传输数据的步骤:
Vue.prototype.bus = new Vue()
this.bus.$emit('change',当前组件的数据)
this.bus.$on('change',一个用于赋值的函数)
下面的代码是点击某个组件发生数据变化时,另外一个组件的数据也发生变化:
<body> <div id="app"> <child-a></child-a> <child-a></child-a> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> // 1. 给Vue的原型添加一个bus,bus是一个Vue实例 Vue.prototype.bus = new Vue() Vue.component('child-a', { data: function() { return { content: 0 } }, methods: { // 2.定义一个函数,可以在数据发生变化时调用emit(这里定义一个点击事件的响应函数) handleClick: function() { this.content+=1 this.bus.$emit('change',this.content) } }, mounted: function(){ var this_= this // 这里要有一个这样的赋值,下面不能直接使用this,由于在函数中this指向的已经不是当前对象了,而用_this保存的才是当前对象 // 3. 在组件中对bus中触发的事件进行监听。(当emit触发事件时会调用) this.bus.$on('change',function(msg){ // 4.函数中的参数是emit触发事件带的参数。 this_.content = msg // 5.修改当前组件中的数据 }) }, template: `<div @click='handleClick'>{{ content }}</div>` }); var app = new Vue({ el: '#app' }) </script>
【有个建议,建议写属性名的时候都使用kebab-case (短横线分隔命名) 命名,由于这个的兼容效果最好】
HTML 中的特性名是大小写不敏感的,因此浏览器会把全部大写字符解释为小写字符。若是你在props中使用了驼峰命名法,那你在定义属性的时候须要使用kebab-case (短横线分隔命名) 命名才能正确传输数据【由于短横线后面的字符能够识别成大写,从而可以匹配到】。
若是在属性中也使用驼峰命名法命名属性的时候会报这样的错:Prop "mycontent" is passed to component
, but the declared prop name is "myContent". Note that HTML attributes are case-insensitive and camelCased props need to use their kebab-case equivalents when using in-DOM templates. You should probably use "my-content" instead of "myContent"
<body> <div id="app"> <!--下面这里会报错 --> <child myContent='hello-1'></child> <!-- 下面会正常 --> <child my-content='hello-2'></child> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('child', { props:['myContent'], template: `<div>{{myContent}}</div>` }); var app = new Vue({ el: '#app' }) </script>
一样的,若是在组件的template属性中使用驼峰命名法的属性,那么这个限制就不存在了。
有时候须要使用第三方的组件的时候,因此会须要传输数据给这个组件来渲染。但如何限制传入的数据的类型呢?
格式:
props: { // 数据名:数据类型 title: String, likes: Number, ... }
若是传入的类型不对,那么会报Invalid prop: type check failed for prop "xxx". Expected String with value "xxx", got Number with value xxx.
的错误。
若是容许多种值,能够定义一个数组:
props: { content: [String,Number] }
咱们也能够给props中的数据设置默认值,若是使用default设置值,那么没有传某个数据时默认使用这个数据。
props: { content: { type:[String,Number], default:'个人默认值' } }
若是使用default给数组或对象类型的数据赋默认值,那么要定义成一个函数。
若是要求某个数据必须传给子组件,那么能够为它设置required。
格式:
props: { content: { type: String, required: true } }
若是没传,会报Missing required prop: "xxx"
的错。
若是想要更精确的校验,可使用validator,里面是一个函数,函数的第一个参数就是传入的值,当函数内返回true时,这个值才会校验经过。
如下的代码是要求传入的字符串长度大于6位的校验:
Vue.component('child', { props: { content: { type: String, validator: function(value) { return (value.length > 6) } } }
若是验证不经过,会报Invalid prop: custom validator check failed for prop "xxx"
的错。
用下面的代码来讲一个问题:
<html lang="en"> <head> <meta charset="utf-8"> <title>demo</title> </head> <body> <div id="app"> <child @click='myFunction'></child> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('child',{ template:` <button>按钮</button> ` }); var vm = new Vue({ el: '#app', methods: { myFunction: function() { console.log('haha') } } }) </script> </html>
上面的代码你会发现点击了按钮却没有调用函数。
而下面的按钮按了会打出child。
<html lang="en"> <head> <meta charset="utf-8"> <title>demo</title> </head> <body> <div id="app"> <child @click='myFunction'></child> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('child',{ template:` <button @click='childFunction'>按钮</button> `, methods: { childFunction: function() { console.log('child') } } }); var vm = new Vue({ el: '#app', methods: { myFunction: function() { console.log('haha') } } }) </script> </html>
下面的代码是使用了emit来达到一样效果的代码:
<html lang="en"> <head> <meta charset="utf-8"> <title>demo</title> </head> <body> <div id="app"> <child @click='myFunction'></child> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('child',{ template:` <button @click="$emit('click')">按钮</button> ` }); var vm = new Vue({ el: '#app', methods: { myFunction: function() { console.log('haha') } } }) </script> </html>
<template>
元素当作不可见的包裹元素,让成组的元素可以统一被渲染出来。<template>
元素当作不可见的包裹元素<template v-if="ok"> <h1>Title</h1> <p>Paragraph 1</p> <p>Paragraph 2</p> </template>
<ul> <template v-for="item in items"> <li>{{ item.msg }}</li> <li class="divider" role="presentation"></li> </template> </ul>
插槽也能够分发多份数据。使用指定的插槽名就能够获取指定的插槽数据。
若是没有数据传过来,那么插槽能够定义一个默认的数据用来显示。
<body> <div id="app"> <child-a> <template slot-scope='props'> <!--2. slot-scope的属性值是能够随意的,表明做用域插槽的名字 --> <h3>{{props.index}}</h3> <!--3. props.xxx 是传过来的值的名字,值须要绑定到slot中 --> </template> </child-a> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.prototype.bus = new Vue() Vue.component('child-a', { data: function() { return { list: [2,4,6,8,10] } }, // 1. 绑定属性,名为index,那么做用域插槽就能够获取名为index的数据 template: `<div> <slot v-for='item of list' :index=item></slot> </div>` }); var app = new Vue({ el: '#app' }) </script>
<component :is='组件名'></component>
若是改变了is属性的值,那么渲染的组件就会发生变化。下面是上图的演示代码:
<body> <div id="app"> <button @click='changeLogin'>切换登陆方式</button> <component :is='loginType'></component> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('userInfoLogin',{ template:` <div> <div>用户名登录:<input type="text"></div> <div>请输入密码:<input type="password"></div> </div> ` }); Vue.component('phoneLogin',{ template:` <div> <div>请输入手机号:<input type="text"></div> <div>请输入验证码:<input type="text"></div> </div> ` }) var vm = new Vue({ el: '#app', data: { loginType:'userInfoLogin' }, methods: { changeLogin: function() { this.loginType = this.loginType === 'userInfoLogin'?'phoneLogin':'userInfoLogin'; } } }) </script>
下面以上面的动态组件切换为例:
若是给上面的登陆组件都加上一个created用来显示渲染次数的话,咱们就能够看到是否是每次切换都会从新渲染。
若是加了keep-alive以后再也不重复输出,那么就说明组件被缓存了。
<body> <div id="app"> <button @click='changeLogin'>切换登陆方式</button><!-- 2.屡次点击切换,查看组件渲染状况 --> <keep-alive> <component :is='loginType'></component> <!-- 4.注意在keep-alive包裹以前和以后的控制台输出状况 --> </keep-alive> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('userInfoLogin',{ template:` <div> <div>用户名登录:<input type="text"></div> <div>请输入密码:<input type="password"></div> </div> `, created: function(){ console.log('userInfoLogin')// 1.用来讲明渲染了userInfoLogin组件,屡次渲染则屡次输出 } }); Vue.component('phoneLogin',{ template:` <div> <div>请输入手机号:<input type="text"></div> <div>请输入验证码:<input type="text"></div> </div> `, created: function(){ console.log('phoneLogin') // 2.用来讲明渲染了phoneLogin组件,屡次渲染则屡次输出 } }) var vm = new Vue({ el: '#app', data: { loginType:'userInfoLogin' }, methods: { changeLogin: function() { this.loginType = this.loginType === 'userInfoLogin'?'phoneLogin':'userInfoLogin'; } } }) </script>
1.在元素中使用ref属性给元素起一个有标识意义的名字。
2.this.\(refs能够获取当前组件实例的全部使用了ref的元素,`this.\)refs.名字`表明指定的元素。
3.而后你能够进行dom操做了。
<body> <div id="app"> <div ref='demo'>1</div> <!-- 1. 设置ref --> <button @click='changeColor'>+1</button> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', methods: { changeColor:function() { console.log(this.$refs.demo.innerText) // 获取ref对象,有了dom对象你就能够进行dom操做了。 this.$refs.demo.style = 'color:red' this.$refs.demo.innerText+=this.$refs.demo.innerText } } }) </script>
refs也能够用在组件上,能够获取组件的数据(但这时候的ref获取的不是一个dom对象,而是一个vue实例对象,因此不能获取innerText这类dom属性)。
<body> <div id="app"> <child ref='demo'></child> <button @click='changeColor'>+1</button> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('child',{ data: function(){ return { msg:'hello world!' } }, template:'<div>hello</div>' }) var app = new Vue({ el: '#app', methods: { changeColor:function() { console.log(this.$refs.demo.msg) // 获取组件实例的数据。 } } }) </script>
若是说前面讲的都是基础,必需要掌握的话,那么动画效果是锦上添花,无关紧要(对于我这些不追求动画效果就不显得重要了),因此这里就不讲了,这里仅仅是为了显示vue能有实现动画效果的功能。
有兴趣的能够看一下官网:
vue官网动画效果直通车