VUE自定义组件
简介
有时候有一组html
结构的代码,并且这个上面可能还绑定了事件。然后这段代码可能有多个地方都被使用到了,如果都是拷贝来拷贝去,很多代码都是重复的,包括事件部分的代码都是重复的。那么这时候我们就可以把这些代码封装成一个组件,以后在使用的时候就跟使用普通的html
元素一样,拿过来用就可以了。
基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>vuedemo</title> <script src= "https://cdn.jsdelivr.net/npm/vue" ></script> </head> <body> <div id= "app" > <button-counter></button-counter> <button-counter></button-counter> <button-counter></button-counter> </div> <script> Vue.component( 'button-counter' , { data: function () { return { count: 0 } }, template: '<button v-on:click="count++">点击了{{ count }}次</button>' }); let vm = new Vue({ el: "#app" , data: {} }); </script> </body> </html> |
以上我们创建了一个叫做button-counter
的组件,这个组件实现了能够记录点击了多少次按钮的功能。后期如果我们想要使用,就直接通过button-counter
使用就可以了。然后因为组件是可复用的Vue
实例,所以它们与new Vue
接收相同的选项,例如data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像el
这样根实例特有的选项。另外需要注意的是:组件中的data必须为一个函数!
官网文档例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>vuedemo</title> <script src= "https://cdn.jsdelivr.net/npm/vue" ></script> </head> <body> <div id= "components-demo" > <button-counter></button-counter> </div> <script> // 定义一个名为 button-counter 的新组件 Vue.component( 'button-counter' , { data: function () { return { count: 0 } }, template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>' }); //组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是 <button-counter>。 // 我们可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用: new Vue({ el: '#components-demo' , }); </script> </body> </html> |
给组件添加属性
像原始的html
元素都有自己的一些属性,而我们自己创建的组件,也可以通过prop
来添加自己的属性。这样别人在使用你创建的组件的时候就可以传递不同的参数了。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>vuedemo</title> <script src= "https://cdn.jsdelivr.net/npm/vue" ></script> </head> <body> <div id= "app" > <blog-post :title= "blog.title" v- for = "blog in blogs" ></blog-post> </div> <script> Vue.component( 'blog-post' , { props: [ 'title' ], template: '<h3>{{ title }}</h3>' }); new Vue({ el: "#app" , data: { blogs: [ { "title" : "想想霸哥怎么做" , "id" : 1}, { "title" : "霸王计划" , "id" : 2}, { "title" : "如何学好Vue" , "id" : 3}, ] } }); </script> </body> </html> |
单一根元素:
如果自定义的组件中,会出现很多html
元素,那么根元素必须只能有一个,其余的元素必须包含在这个根元素中。比如以下是一个组件中的代码,会报错:
1 2 | <h3>{{ title }}</h3> <div v-html= "content" ></div> |
我们应该改成:
1 2 3 4 | <div class = "blog-post" > <h3>{{ title }}</h3> <div v-html= "content" ></div> </div> |
组件中自定义事件
子组件中添加事件跟之前的方式是一样的,然后如果发生某个事件后想要通知父组件,那么可以使用this.$emit
函数来实现。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>vuedemo</title> <script src= "https://cdn.jsdelivr.net/npm/vue" ></script> </head> <body> <div id= 'app' > <blog-item v- for = "blog in blogs" :blog= "blog" @check-changed= "checkChanged" ></blog-item> <h1>选中的博客:</h1> <div v- for = "blog in selected_blogs" > {{blog.title}} </div> </div> <script> Vue.component( "blog-item" , { props: [ 'blog' ], template: ` <div> <span>{{blog.title}}</span> <input type= "checkbox" @click= "onCheck" > </div> `, methods: { onCheck() { this .$emit( "check-changed" , this .blog) } } }); new Vue({ el: '#app' , data: { blogs: [{ title: "如何学好Vue?" , id: "1" }, { title: "前后端分离项目?" , id: "2" }], selected_blogs: [] }, methods: { checkChanged(blog) { // indexOf:获取某个元素在数组中的位置,如果返回值为非负数,那么就是存在,就是下标 // 否则,代表这个元素在数组中不存在 let index = this .selected_blogs.indexOf(blog); if (index >= 0) { this .selected_blogs.splice(index, 1) } else { this .selected_blogs.push(blog) } } } }) </script> </body> </html> |
需要注意的是,因为html
中大小写是不敏感的,所以在定义子组件传给父组件事件名称的时候,不要使用myEvent
这种驼峰命名法,而是使用my-event
这种规则。
上述示例代码调用关系如下:
运行结果于勾选后展示结果:
自定义组件v-model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>vuedemo</title> <script src= "https://cdn.jsdelivr.net/npm/vue" ></script> </head> <body> <div id= 'app' > <Stepper v-model= "goods_count" ></Stepper> </div> <script> // 计步器 Vue.component( "Stepper" , { props: [ 'count' ], // model:用来配置v-model的表现形式 model: { event : "count-changed" , prop: "count" }, template: ` <div> <button @click= "substract" >-</button> <span>{{count}}</span> <button @click= "add" >+</button> </div> `, methods: { substract() { // 这个里面不需要修改this.count的值,只要把结果传出去就可以了 this .$emit( "count-changed" , this .count - 1) }, add() { this .$emit( "count-changed" , this .count + 1) } } }); new Vue({ el: '#app' , data: { goods_count: 0 }, watch: { goods_count: function (newValue, oldValue) { console.log( '==========' ); console.log(newValue); console.log(oldValue); console.log( '==========' ); } } }) </script> </body> </html> |
插槽
我们定义完一个组件后,可能在使用的时候还需要往这个组件中插入新的元素或者文本。这时候就可以使用插槽来实现。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>vuedemo</title> <script src= "https://cdn.jsdelivr.net/npm/vue" ></script> </head> <body> <div id= "app" > <navigation-link url= "/profile/" > 个人中心 </navigation-link> </div> <script> Vue.component( 'navigation-link' , { props: [ 'url' ], template: ` <a v-bind:href= "url" class = "nav-link" > <slot></slot> </a> ` }); new Vue({ el: "#app" }); </script> </body> </html> |
当组件渲染的时候,<slot></slot>
将会被替换为“个人中心”。插槽内可以包含任何模板代码,包括HTML
:
1 2 3 | <navigation-link url= "/profile" > <span style= "color: red" >个人中心</span> </navigation-link> |
如果<navigation-link>
没有包含一个<slot>
元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
作用域
通过外面传给组件的变量,在以后使用插槽的时候是不能使用的。比如以上url
只能在navigation-link
中使用,但是后面使用插槽的时候不能使用。比如
1 2 3 4 5 6 7 8 | <navigation-link url= "/profile" > Clicking here will send you to: {{ url }} <!-- 这里的 `url` 会是 undefined,因为 "/profile" 是 _传递给_ <navigation-link> 的而不是 在 <navigation-link> 组件*内部*定义的。 --> </navigation-link> |
插槽默认值
有时候在使用组件的时候,插槽中绝大部分情况是一种元素。那么我们就可以给插槽提供一个默认值,然后后面如果不想使用这个默认值的时候,就只需要提供自己定义的值就可以了。比如有一个叫做submit-button
的组件,代码如下:
1 2 3 | <button type= "submit" > <slot>提交</slot> </button> |
然后在使用这个组件的时候,可以直接<submit-button></submit-button>
,默认在里面就会显示“提交”文字。如果想要在使用的时候显示其他文字,那么也可以通过<submit-button>保存</submit-button>
来实现。
命名插槽:
自定义组件中可以有多个插槽,这时候就需要通过名字来进行区分了。其实如果没有指定名字,默认是有一个名字叫做default
的。比如我们有一个名叫container
的自定义组件:
1 2 3 4 5 6 7 8 9 10 11 | <div class = "container" > <header> <slot name= "header" ></slot> </header> <main> <slot></slot> </main> <footer> <slot name= "footer" ></slot> </footer> </div> |
以后在使用这个组件的时候使用v-slot:插槽名
的方式来加载不同的数据:
1 2 3 4 5 6 7 8 9 | <container> <template v-slot:header> 这是头部信息 </template> 这是主要部分的信息 <template v-slot:footer> 这是网页尾部信息 </template> </container> |
插槽作用域:
默认在插槽中的代码只能使用全局Vue
中的属性,如果想要使用自定义组件中的属性,那么需要在定义slot
的时候使用v-bind
来进行绑定。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>vuedemo</title> <script src= "https://cdn.jsdelivr.net/npm/vue" ></script> </head> <body> <div id= "app" > <sub-nav v-slot= "slotProps" > 当前点击:{{slotProps.index}} </sub-nav> </div> <script> Vue.component( 'sub-nav' , { props: [ 'url' ], data: function () { return { navs: [ '网络设置' , '路由设置' , '设备管理' ], index: 0 } }, methods: { indexBtnClick: function (index) { this .index = index; } }, template: ` <div class = "container" > <button v- for = "(nav,index) in navs" @click= "indexBtnClick(index)" v-bind:key= "index" >{{nav}}</button> <slot v-bind:index= "index" ></slot> </div> ` }); new Vue({ el: "#app" }); </script> </body> </html> |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了