Vue组件
作者:@skyflask
转载本文请注明出处:https://www.cnblogs.com/skyflask/p/10907415.html
目录
一、组件概述
1.什么是组件?
二、组件定义
1、组件定义步骤
2、全局组件
3、局部组件
三、父子组件
四、组件简化
1、template方式简化
2、script方式简化
五、组件间通讯
1、单层通讯
2、多层通讯
六、自定义事件
七、插槽slot
1、匿名插槽
2、具名插槽
八、综合案例
组件以及父子组件传值
九、组件的注意事项
1、is属性
2、子组件的data
3、ref使用
4、父子传值
5、props和非props特性
6、给组件绑定原生事件
一、组件概述
1.什么是组件?
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。
所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子。
二、组件定义
1、组件定义步骤
组件定义2步骤:
a、创建组件构造器
Vue.extend()定义要渲染的HTML
b、注册组件
Vue.component()定义组件名和构造器
调用Vue.extend()创建的是一个组件构造器,构造器有一个选项对象,选项对象的template属性用于定义组件要渲染的HTML;
调用Vue.component()注册组件时,需要提供2个参数:组件的标签名 和 组件构造器;注册的组件要挂载到某个Vue实例下,否则它不会生效;
Vue.extend() 和 Vue.component():由于 Vue 本身是一个构造函数,Vue.extend() 是一个类继承方法,它用来创建一个 Vue 的子类并返回其构造函数; 而Vue.component() 的作用在于:建立指定的构造函数与 ID 字符串间的关系,从而让 Vue.js 能在模板中使用它;直接向 Vue.component() 传递 options 时,它会在内部调用 Vue.extend()。
2、全局组件
全局组件定义2个步骤:
// 1.创建组件构造器
let Profile = Vue.extend({
// 1.1 模板选项
template: `
<div>
<input type="date">
<p>今天已经是冬天了!</p>
</div>
`
});
// 2. 注册一个全局的组件,任何实例都可以使用它
Vue.component('my-date', Profile);
全局组件可以直接在每一个vue实例中使用。
3、局部组件
// 1.创建组件构造器
let Profile = Vue.extend({
// 1.1 模板选项
template: `
<div>
<input type="date">
<p>今天已经是冬天了!</p>
</div>
`
});
//2、注册到一个指定的Vue实例中,这样就只能在某一个实例中使用这个组件了
new Vue({
el: '#app',
//通过注册到特定的实例,来达到局部应用,本例中只能应用在id=app的实例中
components: {
'my-date': Profile,
}
});
注意:
-
全局组件需要单独注册;
-
局部组件必须注册到特定的实例中,以components属性的方式;
三、父子组件
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 | // 1. 子组件构造器 let Child1 = Vue.extend({ template: `<img src= "img/img_01.jpg" width= "200" >` }); let Child2 = Vue.extend({ template: `<p>我认为自己很美!</p>` }); //如果想单独使用子组件,必须单独注册。这样就可以作为一个单独的全局组件使用。 Vue.component( 'child' , Child1); // 2. 父组件构造器 Vue.component( 'parent' , { components: { 'my-child1' : Child1, 'my-child2' : Child2 }, template: ` <div> <my-child1></my-child1> <my-child2></my-child2> </div> ` }); //3、使用 <div id= "app" > <parent></parent> //child为全局组件,可以单独使用! <child></child> </div> |
四、组件简化
1、template方式简化
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 | <body> <div id= "app" > <my-div></my-div> </div> <template id= "my_div" > //template里面只能有一个根,也就是所有内容都包含在一个div里面! <div> <div>我是MT!</div> <input type= "date" > <img src= "img/img_02.jpg" alt= "" width= "400" > </div> </template> <script src= "js/vue.js" ></script> <script> // 1. 实例化组件 Vue.component( 'my-div' , { template: '#my_div' }); new Vue({ el: '#app' , }); </script> </body> |
2、script方式简化
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 | <body> <div id= "app" > <my-div></my-div> </div> <script type= "text/x-template" id= "my_div" > <div> <img src= "img/img_02.jpg" alt= "" width= "200" > <p>我是花姑娘!</p> </div> </script> <script src= "js/vue.js" ></script> <script> // 1. 实例化组件 Vue.component( 'my-div' , { template: '#my_div' }); new Vue({ el: '#app' , }); </script> </body>注意事项: 1. 组件挂载数据必须是函数,返回一个对象 |
1 |
1 2 3 4 5 6 7 8 | Vue.component( 'my-btn' , { template: '#my_btn' , data(){ return { counter: 0 } } }); |
1 |
1 |
组件挂载数据,必须是一个函数,且返回一个对象,这样子组件被任何对象引用时,data就互不干涉,都是一个独立的对象,可以随时修改,数据都互不影响。
2、组件的template中,必须包含在一个根下面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <template id= "my_div" > //template里面只能有一个根,也就是所有内容都包含在一个div里面! <div> <div>我是MT!</div> <input type= "date" > <img src= "img/img_02.jpg" alt= "" width= "400" > </div> </template> <template id= "my_div" > //这是错误的,不能有多个根元素! <div>我是MT!</div> <input type= "date" > <img src= "img/img_02.jpg" alt= "" width= "400" > </template> |
五、组件间通讯
1、单层通讯
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 | a、父子通信 <body> <div id= "app" > //第三步、父组件给子组件传值,单层传值无需动态绑定。父子传值,让子组件更活起来! <my-div message= "今天要下雨" imgsrc= "img/img_02.jpg" ></my-div> <my-div message= "明天要下冰雹" imgsrc= "img/img_01.jpg" ></my-div> </div> <template id= "my_div" > //第一步:子组件定义接受的值 <div> <h1>{{message}}</h1> <img :src= "imgsrc" width= "200" alt= "" > </div> </template> <script src= "js/vue.js" ></script> <script> // 1. 创建组件, //第二步、通过props定义从父组件能够接受的值 Vue.component( 'my-div' , { template: '#my_div' , props: [ 'message' , 'imgsrc' ] //注意,要支持驼峰式imgSrc的写法,需要在template和父组件里面修改成img-src或img-Src。 }); new Vue({ el: '#app' , data: { msg: '今天的天气很好!' } }); </script> </body> |
2、多层通讯
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 | <body> //4、祖父组件使用和传值 <div id= "app" > <my-parent :imgtitle= "title" :imgsrc= "img" ></my-parent> </div> <template id= "my_img" > <img :src= "imgsrc" width= "200" > </template> <template id= "my_title" > <h2>{{title}}</h2> </template> //3、父组件定义和传值 <template id= "my_parent" > <div> <child1 :childimg= "imgsrc" ></child1> <child2 :childtitle= "imgtitle" ></child2> </div> </template> <script src= "js/vue.js" ></script> <script> // 1. 子组件的实例 let Child1 = Vue.extend({ template: '#my_img' , props: [ 'childmyimg' ] #父组件传递属性 }); let Child2 = Vue.extend({ template: '#my_title' , props: [ 'childtitle' ] }); // 2. 子组件注册到父组件 Vue.component( 'my-parent' , { props: [ 'imgtitle' , 'imgsrc' ], #曾祖父传递的属性 components: { 'child1' : Child1, 'child2' : Child2 }, template: '#my_parent' }); new Vue({ el: '#app' , data: { title: '我是不是很漂亮' , img: 'img/img_01.jpg' } }); </script> </body> |
祖父组件=>父组件=>子组件,注意事项:
1、多层传值必须是动态绑定的方式 :
2、相对子组件必须通过props属性接受父组件传过来的数据
六、自定义事件
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 | 子组件通知给外界,他执行了什么操作,此时,外界会做出相应的操作。 <body> <div id= "app" > <!--父组件通过 on 监听total事件,一旦执行total事件,则会修改Vue实例中的数据,这样就达到了在父组件中多次调用子组件,数据都是独立的--> <my-btn @total= "allcounter()" ></my-btn> <my-btn @total= "allcounter()" ></my-btn> <my-btn @total= "allcounter()" ></my-btn> <my-btn @total= "allcounter()" ></my-btn> <my-btn @total= "allcounter()" ></my-btn> <my-btn @total= "allcounter()" ></my-btn> <my-btn @total= "allcounter()" ></my-btn> <p>所有按钮一共点击了{{totalCounter}}次</p> </div> <template id= "my_btn" > <button @click= "total()" >点击了{{counter}}次</button> </template> <script src= "js/vue.js" ></script> <script> //定义子组件 Vue.component( 'my-btn' , { template: '#my_btn' , data(){ return { counter: 0 //注意:在子组件中挂载数据,必须返回对象,这样在任何地方调用子组件都是独立的,数据互不干涉。 } }, methods: { total(){ this .counter += 1; // 通知外界,我触发了total方法,然后他就会调用后面的allcounter(),这样就把所有的点击次数加起来了 this .$emit( 'total' ); } } }); new Vue({ el: '#app' , data: { totalCounter: 0 }, methods: { allcounter(){ this .totalCounter += 1; } } }); </script> </body> |
使用 $on(eventName) 监听事件
使用 $emit(eventName) 触发事件
七、插槽slot
slot的意思是插槽,其目的在于让组件的可扩展性更强。打个比方说:假如父组件需要在子组件内放一些DOM,那么这些DOM是显示、不显示、在哪个地方显示、如何显示,就是slot分发负责的。
注意:如果使用子组件时,在里面添加DOM,默认这些Dom是不会显示的,因为子组件根本不认识。例如:
1 | <child><div>我想在子组件中显示,但是显示不出来</div></child> |
插槽就像电路板,是用来方便插入内容的。slot分为匿名插槽和具名插槽。
匿名插槽就像公交车,谁都可以上,任何人都可以使用这个插槽,很类似娱乐圈中的女明星啦;
具名插槽则是私家车,一个人对应一辆车,一个内容对应一个插槽,这个就是生活中的良家妇女啦。
1、匿名插槽
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 | <body> <div id= "app" > <my-slot> <!--date会覆盖默认的插槽--> <input type= "date" > </my-slot> </div> <template id= "my_slot" > <div id= "panel" > <h2 class = "panel-header" >插槽的头部</h2> <!--预留一个插槽--> <slot>可以替换任何标签,默认显示提示的内容</slot> <footer>插槽的尾部</footer> </div> </template> <script src= "js/vue.js" ></script> <script> Vue.component( 'my-slot' , { template: '#my_slot' }); new Vue({ el: '#app' , data: { msg: '今天的天气很好!' } }); </script> </body> |
2、具名插槽
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 | <body> <div id= "app" > <my-computer> <!--只能根据slot的name插入对应的数据--> <div slot= "cpu" >Inter Core i8</div> <img slot= "gpu" src= "img/img_02.jpg" width= "100" alt= "" > <div slot= "memory" >128G</div> <input type= "color" slot= "hard-drive" > </my-computer> </div> <template id= "my_computer" > <div id= "main" > <slot name= "cpu" >这里是插cpu的</slot> <slot name= "gpu" >这里是插gpu的</slot> <slot name= "memory" >这里是插内存条的</slot> <slot name= "hard-drive" >这里是插硬盘的</slot> </div> </template> <script src= "js/vue.js" ></script> <script> Vue.component( 'my-computer' , { template: '#my_computer' }); new Vue({ el: '#app' , data: { msg: '今天的天气很好!' } }); </script> </body> |
八、综合案例
组件以及父子组件传值
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title></title> </head> <body> <div id= "app" > <div> <input type= "text" v-model= "inputValue" > <button @click= "handleClickBtn" >添加</button> </div> <ul> <todo-item v- for = "(item,index) in list" :content= "item" :key= "item" :index= "index" :msg= "msg" @delete= "handleParentDelete" > {{item}} </todo-item> </ul> </div> <script src= "js/vue.js" ></script> <script> //定义全局组件todoItem var todoItem = { //子组件接受来自父组件的传值,可以直接使用 props: [ 'content' , 'index' , 'msg' ], template: "<li @click='handleItemDelete'>{{content}}-{{msg}}</li>" , methods: { handleItemDelete: function () { //当点击子组件时,会向外触发delete事件,同时带上对应的参数,父组件监听到delete被触发后,则会执行对应的方法 this .$emit( 'delete' , this .index) } }, } // 1. 创建Vue的实例 let vm = new Vue({ el: '#app' , data: { list:[], inputValue: '' , msg: '我是来自父组件的msg' }, components:{ //全局组件注册到vm实例中 todoItem }, methods:{ handleClickBtn:function(){ if ( this .inputValue){ this .list.push( this .inputValue); this .inputValue = '' ; } else { alert( '输入内容不能为空!' ); } }, handleParentDelete:function(id){ //$emit传给父组件的参数,在这里接受。 alert(id); this .list.splice(id,1); }, } }); </script> </body> </html> |
VUE的组件其实也是一个Vue实例,她具备vue的所有属性和方法。
九、组件的注意事项
1、is属性
解决h5的一些小bug
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 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title></title> </head> <body> <div id= "app" > <table> <tbody> //tbody标签直接使用row会在h5中有问题,解析后会跳到table外面去 <row></row> <row></row> <row></row> //修改为: <tr is = "row" ></tr> <tr is = "row" ></tr> <tr is = "row" ></tr> //ul标签里面需要使用li,select标签中需要使用option </tbody> </table> </div> <script src= "js/vue.js" ></script> <script> Vue.component( 'row' ,{ template: '<tr><td>this is a row</td></tr>' }) // 1. 创建Vue的实例 let vm = new Vue({ }); </script> </body> </html> |
2、子组件的data
组件中的data必须是一个函数,返回一个对象
1 2 3 4 5 | data:function(){ return { number:0 } }, |
3、ref使用
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 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title></title> </head> <body> <div id= "app" > <counter ref = "one" @change= "handleChange" ></counter> <counter ref = "two" @change= "handleChange" ></counter> <div>{{total}}</div> </div> <script src= "js/vue.js" ></script> <script> Vue.component( 'counter' ,{ template: '<div @click="handleClick">{{number}}</div>' , data:function(){ return { number:0 } }, methods:{ handleClick:function(){ this .number++ this .$emit( 'change' ) } } }) // 1. 创建Vue的实例 let vm = new Vue({ el: '#app' , data:{ total:0 }, methods:{ handleChange:function () { this .total = this .$refs.one.number + this .$refs.two.number } } }); </script> </body> </html> |
4、父子传值
父组件传值给子组件,子组件最好不要修改父组件的数据,因为加入父组件传递的是一个对象,可能被多个子组件反复的修改,导致数据混乱。
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 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title></title> </head> <body> <div id= "app" > <counter :count= '1' @change= 'handleChange' ></counter> <counter :count= '2' @change= 'handleChange' ></counter> <span>{{total}}</span> </div> <script src= "js/vue.js" ></script> <script> var counter={ props:[ 'count' ], template: "<div @click='handleClick'>{{number}}</div>" , data:function(){ return { //拷贝一个父本出来 number: this .count } }, methods:{ handleClick:function(){ //注意:避免直接修改父组件传过来的数据 //this.count++ //每次步长为1 this .number += 1 //每次子组件被点击后都告知父组件触发对应的事件 this .$emit( 'change' ,1) } } } // 1. 创建Vue的实例 let vm = new Vue({ el: '#app' , components:{ counter }, data:{ total:3 }, methods:{ handleChange:function(num){ this .total+=num } } }); </script> </body> </html> |
5、props和非props特性
子组件对父组件传过来的数据进行约束检测,则为props特性
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 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title></title> </head> <body> <div id= "app" > <child content= "hello world" ></child> </div> <script src= "js/vue.js" ></script> <script> Vue.component( 'child' ,{ props:{ content:{ type:String, required: false , default : 'default value' , validator:function (value) { return (value.length>5) } } }, template: '<div>{{content}}</div>' , }) // 1. 创建Vue的实例 let vm = new Vue({ el: '#app' , }); </script> </body> </html> |
props特点:
- 1、父组件传递过来
- 2、子组件必须验证
- 3、子组件可以使用
- 4、不会渲染在子组件中
父组件向子组件传递数据,但是在子组件没有明确接受,则不能通过{{}}使用,也称之为非props
非props特点:
- 1、父组件传递过来
- 2、子组件没明确写在props里面接受
- 3、子组件不能通过{{}}使用
- 4、会渲染在子组件当中,比如上面的content='hello wold‘会在子组件元素的html属性中渲染’。
6、给组件绑定原生事件
正常情况下,在父组件中绑定事件,我们只能在子组件通过this.$emit('events')进行触发后执行:
1 | <child @click= 'handleClick' ></child><br>如果需要在父组件中也生效,需要通过给父组件绑定原生事件: |
1 | <child @click.native= 'handleClick' ></child><br>完整示例如下: |
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></title> </head> <body> <div id= "app" > <child @click.native= 'handleClick' ></child> </div> <script src= "js/vue.js" ></script> <script> Vue.component( 'child' ,{ template: '<div>Child</div>' , }) // 1. 创建Vue的实例 let vm = new Vue({ el: '#app' , methods:{ handleClick:function(){ alert( 'aaa' ) } } }); </script> </body> </html> |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」