简介
有时候有一组html
结构的代码,并且这个上面可能还绑定了事件。然后这段代码可能有多个地方都被使用到了,如果都是拷贝来拷贝去,很多代码都是重复的,包括事件部分的代码都是重复的。那么这时候我们就可以把这些代码封装成一个组件,以后在使用的时候就跟使用普通的html
元素一样,拿过来用就可以了。
基本使用
<!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必须为一个函数!
官网文档例子:
<!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
来添加自己的属性。这样别人在使用你创建的组件的时候就可以传递不同的参数了。示例代码如下:
<!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
元素,那么根元素必须只能有一个,其余的元素必须包含在这个根元素中。比如以下是一个组件中的代码,会报错:
<h3>{{ title }}</h3> <div v-html="content"></div>
我们应该改成:
<div class="blog-post"> <h3>{{ title }}</h3> <div v-html="content"></div> </div>
组件中自定义事件
子组件中添加事件跟之前的方式是一样的,然后如果发生某个事件后想要通知父组件,那么可以使用this.$emit
函数来实现。示例代码如下:
<!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
<!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>
插槽
我们定义完一个组件后,可能在使用的时候还需要往这个组件中插入新的元素或者文本。这时候就可以使用插槽来实现。示例代码如下:
<!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
:
<navigation-link url="/profile"> <span style="color: red">个人中心</span> </navigation-link>
如果<navigation-link>
没有包含一个<slot>
元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
作用域
通过外面传给组件的变量,在以后使用插槽的时候是不能使用的。比如以上url
只能在navigation-link
中使用,但是后面使用插槽的时候不能使用。比如
<navigation-link url="/profile"> Clicking here will send you to: {{ url }} <!-- 这里的 `url` 会是 undefined,因为 "/profile" 是 _传递给_ <navigation-link> 的而不是 在 <navigation-link> 组件*内部*定义的。 --> </navigation-link>
插槽默认值
有时候在使用组件的时候,插槽中绝大部分情况是一种元素。那么我们就可以给插槽提供一个默认值,然后后面如果不想使用这个默认值的时候,就只需要提供自己定义的值就可以了。比如有一个叫做submit-button
的组件,代码如下:
<button type="submit"> <slot>提交</slot> </button>
然后在使用这个组件的时候,可以直接<submit-button></submit-button>
,默认在里面就会显示“提交”文字。如果想要在使用的时候显示其他文字,那么也可以通过<submit-button>保存</submit-button>
来实现。
命名插槽:
自定义组件中可以有多个插槽,这时候就需要通过名字来进行区分了。其实如果没有指定名字,默认是有一个名字叫做default
的。比如我们有一个名叫container
的自定义组件:
<div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>
以后在使用这个组件的时候使用v-slot:插槽名
的方式来加载不同的数据:
<container> <template v-slot:header> 这是头部信息 </template> 这是主要部分的信息 <template v-slot:footer> 这是网页尾部信息 </template> </container>
插槽作用域:
默认在插槽中的代码只能使用全局Vue
中的属性,如果想要使用自定义组件中的属性,那么需要在定义slot
的时候使用v-bind
来进行绑定。示例代码如下:
<!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>