Component组件是Vue.js最强大的功能之一,它能够扩展HTML元素,封装可重用的代码(减小代码冗余)。
在较高层面上,组件是自定义元素。好比说,咱们本身发明了标签,别人不承认,可是vue可以帮咱们去解析,帮咱们把它转换成你们都能承认的。javascript
咱们开发组件的时候,须要告诉vue,咱们建立了一个新的组件,这里咱们就须要注册。
注册分为局部注册和全局注册。html
就是说咱们能够注册一个组件,把它挂载到vue的全局变量上。
它里面有个属性叫Vue.component(组件名称,组件定义)。
咱们能够简单的来测试一下:vue
<body> <div class="container"> <!-- 引用自定义组件 --> <sf-line></sf-line> <!-- 若是咱们要写5个这样的组件,那么咱们能够用v-for --> <sf-line v-for="n in 5"></sf-line> <!-- 若是咱们想要在sf-line标签中直接插入值,能够用slot插槽 --> <sf-line>你好</sf-line> </div> <script> // 在建立vue对象以前,注册组件 // 经过全局注册Vue.component(组件名称,组件定义),调用组件 // 组件名称是自定义的 // 组件定义template(模板),封装html标签,它里面是多个html的综合体,以反引号包裹html内容,(反引号的好处是能够任意回车,不用作字符串的拼接) Vue.component('sf-line',{ template:` <div style="background-color:orange;border-radius:3px;padding:.5em 1em;margin:.5em"> <span>hello</span> <!-- slot 插槽,能够用来接受sf-line中的值--> <span><slot></slot></span> </div> ` }); //建立vue的实例 new Vue({ el:'.container', data:{ } }); </script> </body>
这就是一个最简单的全局注册的组件。java
如今,咱们不但愿将<span>hello</span>中的值写死,咱们但愿可以动态改变span标签中的值,即外部组件(咱们能够把.container理解为一个根组件)向内部组件传值。此时咱们能够借用一个属性props。
props表明属性,也就是说,外部能够传参数,咱们经过props来接收。node
<body> <div class="container"> <!-- 动态绑定属性 属性的绑定用v-bind 咱们绑定一个在props中定义的属性名, 它的属性值为vue实例data中,定义的lineText,而且lineText会被看成变量来解析 --> <sf-line v-bind:node="lineText"></sf-line> <!-- 简写 --> <sf-line :node="lineText"></sf-line> <!-- 咱们也能够定义一个属性,不用v-bind --> <sf-line node="组件"></sf-line> <!-- 若是咱们这样写,lineText会被直接看成一个值打印出来,不会被看成变量解析 --> <sf-line node="lineText"></sf-line> </div> <script> //注册组件 Vue.component('sf-line',{ template:` <div style="background-color:orange;border-radius:3px;padding:.5em 1em;margin:.5em"> <!--接收到props中的值以后,经过双大括号来取出,双大括号中的值会被看成变量来解析--> <span>{{node}}</span> </div> `, // 接收参数,假如参数为node props:['node'] }); //建立vue的实例 new Vue({ el:'.container', // 在.container中,咱们调用了一个子组件<sf-line>,如今咱们能够在vue实例的data中,定义一个值,好比说lineText data:{ lineText:'component' } }); </script> </body>
这里咱们要补充一下外部组件与内部组件传值的机制,这里咱们要记住:
组件是彻底独立的,它有本身的data,methods,钩子函数等,而且这些内容都包含在组件定义中——Vue.component(组件名称,组件定义)。函数
<body> <div class="container"> <!-- 这是vue实例中的msg --> <p>{{msg}}</p> <!-- 这里显示组件中的msg --> <sf-line></sf-line> </div> <script> // 注册组件,组件必定要写在vue实例上面 Vue.component('sf-line',{ template:` <div> <!-- 组件是彻底独立的,因此组件中的msg调用的就是组件data中的msg --> <span>{{msg}}</span> </div> `, // 用来接收外部组件向内部组件传值 props:[ ], // 组件样式中,一样有data,methods,钩子函数等... // 组件中的data是一个方法(不是属性),要有返回值return // data方法会返回一个对象,保证它是多实例的 // 若是像vue实例中data:{}这样写,那么它就是单例的,它会出现一个问题,好比说:组件会应用屡次,若是改变其中一个组件,那么其余组件也会变,这就是单例的 // 咱们但愿这个组件是彻底独立的,因此咱们要return{...},也就是说:咱们每次调用data的时候,都会新建一个对象 data(){ return{ msg:'这是组件中的msg' } }, methods:{ }, created(){ console.log('created'); } }); //建立vue的实例 new Vue({ el:'.container', data:{ msg:'这是vue实例中的msg' } }); </script> </body>
了解了组件传值以后,咱们来学习一下如何来给组件绑定事件。
咱们根据下面代码来学习:学习
<body> <div class="container"> <sf-line :node="lineText"></sf-line> </div> <script> Vue.component('sf-line',{ template:` <div style="background-color:orange;border-radius:3px;padding:.5em 1em;margin:.5em;position:relative"> <span>{{node}}</span> <!-- 目标:当点击关闭时,div消失 --> <!-- 第一种方式:在父元素的角度上,将它隐藏 --> <!-- 第二种方式:子元素本身将本身隐藏 --> <!-- 父组件:.container ; 子组件:<sf-line> --> <span style="position:absolute;right:1em;cursor:pointer">关闭</span> </div> `, props:['node'], methods:{ } }); new Vue({ el:'.container', data:{ lineText:'component' } }); </script> </body>
这里咱们主要讲解
第一种方式:在父元素的角度上,将它隐藏测试
<body> <div class="container"> <sf-line :node="lineText"></sf-line> </div> <script> Vue.component('sf-line',{ template:` <!-- 咱们在div中添加一个v-show,将隐藏操做放在这上面,在组件data中定义它是否显示 --> <div v-show="isShow" style=" background-color:orange; border-radius:3px; padding:.5em 1em; margin:.5em; position:relative" > <span>{{node}}</span> <!-- 点击下面按钮关闭 因为close是组件内部函数,因此在组件的methods中定义属性 --> <span @click="close" style=" position:absolute; right:1em; cursor:pointer" >关闭</span> </div> `, props:['node'], data(){ return{ // 默认状况下让它显示 isShow:true } }, methods:{ close(){ // 当点击close时,让它隐藏 this.isShow=false; } } }); new Vue({ el:'.container', data:{ lineText:'component' }, methods:{ } }); </script> </body>
此刻咱们点击的是外部<sf-line>标签的事件,是在component里面,可是外部vue实例中的methods并无定义方法。也就是说,子组件本身偷偷摸摸就没了,可是父组件根本就不知道。
若是说,父组件这时候想要在子组件被删除以后,从新刷新整个页面。即子组件要通知外面的父组件,它点击了按钮。那么咱们如今应该怎么作?
这里有一个知识点,子组件向父组件传递事件,相似于一种事件传递,但此处叫事件发射。this
这里代码能够这样完善:spa
<body> <div class="container"> <!-- 父组件此时要接收子组件发射的自定义事件 v-on此时监听的是子组件发射的事件close, 咱们为它在父组件中绑定一个方法closeHandler --> <sf-line :node="lineText" @close="closeHandler"></sf-line> </div> <script> Vue.component('sf-line',{ template:` <!-- 咱们在div中添加一个v-show,将隐藏操做放在这上面,在组件data中定义它是否显示 --> <div v-show="isShow" style=" background-color:orange; border-radius:3px; padding:.5em 1em; margin:.5em; position:relative" > <span>{{node}}</span> <!-- 点击下面按钮关闭 因为close是组件内部函数,因此在组件的methods中定义属性 --> <span @click="close" style=" position:absolute; right:1em; cursor:pointer" >关闭</span> </div> `, props:['node'], data(){ return{ // 默认状况下让它显示 isShow:true } }, methods:{ close(){ // 通知父元素 //(子组件向父组件发射emit自定义事件close) this.$emit('close'); // 当点击close时,让它隐藏 this.isShow=false; } } }); new Vue({ el:'.container', data:{ lineText:'component' }, methods:{ // 父组件中绑定的方法 closeHandler(){ alert('closeHandler'); } } }); </script> </body>
局部注册没必要将每一个组件都注册到全局。咱们能够经过某个Vue实例或组件的实例的选项components注册在,仅在其做用域中可用的组件上。
<body> <div class="container"> <sf-list :node="msg"></sf-list> </div> <script type="text/javascript"> //我声明的组件 let sfList={ template:` <div>{{node}}</div> `, props:['node'] }; new Vue({ el:'.container', data:{ msg:'hello component' }, // 局部注册 components:{ // 能够直接将组件定义写在这里面 // 可是,为了避免使代码显得臃肿,我在上面声明一个组件sfList // 将上面注册的组件声明进来 // 属性值:自定义的组件名 // 属性值:上面声明的组件名 'sf-list':sfList /* sfList:sfList 这里也能够用驼峰式命名 */ /* sfList 直接这样简写也是能够的 */ //以上三种方式:<sf-list :node="msg"></sf-list>均可以解析 } }); </script> </body>
插槽定义在组件中,用于接受组件内部的内容。
在全局注册的第一个例子里面咱们简单的用了slot插槽。
这种直接插入,不须要命名的插槽,咱们称为匿名插槽。
咱们先来简单回顾一下:
<body> <div class="container"> <!-- 在组件中,用slot接收插入的内容 --> <sf-container>hello</sf-container> </div> <script type="text/javascript"> //注册组件 Vue.component('sf-container', template` <div class="sf-container"> <div class="sf-header"> <!-- 用slot接收插入的内容 --> <slot></slot> </div> <div class="sf-content"></div> <div class="sf-footer"></div> </div> `, ); </script> </body>
在一个模板中,能够出现不少插槽。
咱们要去区分它们,咱们能够给它取个名字,叫作具名插槽。
<body> <div class="container"> <sf-container> <!-- 此时的属性值与slot中命名的名字相同 --> <div slot="header">header</div> <div slot="content">content</div> <div slot="footer">footer</div> </sf-container> </div> <script type="text/javascript"> //注册组件 Vue.component('sf-container', template` <div class="sf-container"> <div class="sf-header"> <!-- 为不一样的插槽取不一样的名字 --> <slot name="header"></slot> </div> <div class="sf-content"> <slot name="content"></slot> </div> <div class="sf-footer"> <slot name="footer"></slot> </div> </div> `, ); </script> </body>
组件中除了具名插槽外,还有一种插槽,就是做用域插槽。
<body> <div class="container"> <!-- 调用一个组件,里面绑定属性stuList,将studentList中的值传给stuList --> <sf-list :stulist="studentList"> <!-- 提供一个模板,它里面可让咱们定义td的具体格式 --> <!-- slot-scope用于将元素或组件表示为做用域插槽,此属性不支持动态绑定 --> <!-- 属性名自定义 --> <template slot-scope="stuScope"> <!-- 在这里显示插入的内容 --> <!-- 咱们能够在这里选择要插入的数据 --> <!-- 做用域插槽,至关于为咱们多了一个定制的功能 --> {{stuScope.foo.id}} <a href="#">{{stuScope.foo.name}}</a> </template> </sf-list> </div> <script type="text/javascript"> //注册组件 Vue.component('sf-list',{ template:` <div> <p>原来的写法</p> <ul> <li v-for="item in stulist">{{item.name}}</li> </ul> <p>做用域插槽</p> <ul> <li v-for="item in stulist"> <!-- 定义一个插槽,在里面随意绑定一个值foo--> <slot :foo="item"></slot> </li> </ul> </div> `, // 接收studentList中的内容(外部组件向内部组件传值) // props接收的值不区分大小写,不要用驼峰式命名,不然可能报错 props:['stulist'] }); //建立实例 new Vue({ el:'.container', data:{ studentList:[{ id:1, name:'terry' },{ id:2, name:'larry' },{ id:3, name:'tom' }] } }); </script> </body>