本教材由知了传课辛苦制作而成,仅供学习使用,请勿用于商业用途!如进行转载请务必注明出处!谢谢!

自定义组件

有时候有一组html结构的代码,并且这个上面可能还绑定了事件。然后这段代码可能有多个地方都被使用到了,如果都是拷贝来拷贝去,很多代码都是重复的,包括事件部分的代码都是重复的。那么这时候我们就可以把这些代码封装成一个组件,以后在使用的时候就跟使用普通的html元素一样,拿过来用就可以了。

基本使用:

<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>' }); Vue.createApp({ el: "#app", data: {} }).mount("#app"); </script>

以上我们创建了一个叫做button-counter的组件,这个组件实现了能够记录点击了多少次按钮的功能。后期如果我们想要使用,就直接通过button-counter使用就可以了。然后因为组件是可复用的Vue实例,所以它们与Vue.createApp接收相同的选项,例如datacomputedwatchmethods以及生命周期钩子等。仅有的例外是像el这样根实例特有的选项。另外需要注意的是:组件中的data必须为一个函数!

给组件添加属性:

像原始的html元素都有自己的一些属性,而我们自己创建的组件,也可以通过prop来添加自己的属性。这样别人在使用你创建的组件的时候就可以传递不同的参数了。示例代码如下:

<div id="app"> <blog-post v-for="blog in blogs" :title="blog.title"></blog-post> </div> <script> Vue.component('blog-post', { props: ['title'], template: '<h3>{{ title }}</h3>' }) Vue.createApp({ el: "#app", data: { blogs: [ {"title":"钢铁是怎样练成的?","id":1}, {"title":"AI会毁灭人类吗?","id":2}, {"title":"如何学好Vue!","id":3}, ] } }).mount("#app"); </script>

单一根元素:

如果自定义的组件中,会出现很多html元素,那么根元素必须只能有一个,其余的元素必须包含在这个根元素中。比如以下是一个组件中的代码,会报错:

<h3>{{ title }}</h3> <div v-html="content"></div>

我们应该改成:

<div class="blog-post"> <h3>{{ title }}</h3> <div v-html="content"></div> </div>

子组件事件和传递事件到父组件:

子组件中添加事件跟之前的方式是一样的,然后如果发生某个事件后想要通知父组件,那么可以使用this.$emit函数来实现。示例代码如下:

<div id="app"> <blog-post v-for="blog in blogs" :post="blog" :key="blog.id" v-on:like-changed="outerLikeChanged"></blog-post> </div> <script> Vue.component('blog-post', { props: ['post'], template: ` <div> <h3>{{ post.title }}</h3> <input type="checkbox" v-model="post.like" v-on:change="innerLikeChanged"> </div> `, methods: { innerLikeChanged: function(){ this.$emit("like-changed",this.post.id); } } }) Vue.createApp({ el: "#app", data: { blogs: [ {"title":"钢铁是怎样练成的?","id":1,"like":false}, {"title":"AI会毁灭人类吗?","id":2,"like":false}, {"title":"如何学好Vue!","id":3,"like":false}, ] }, methods: { outerLikeChanged: function(post_id){ this.blogs.forEach(blog => { if(blog['id'] == post_id){ blog.like = !blog.like console.log(blog); } }); } } }).mount("#app"); </script>

需要注意的是,因为html中大小写是不敏感的,所以在定义子组件传给父组件事件名称的时候,不要使用myEvent这种驼峰命名法,而是使用my-event这种规则。

自定义组件v-model

一个组件上的v-model默认会利用名为valueprop(属性)和名为input的事件,但是像单选框、复选框等类型的输入控件可能会将value特性用于不同的目的。这时候我们可以在定义组件的时候,通过设置model选项可以用来实现不同的处理方式:

<div id="app"> <blog-post v-for="blog in blogs" v-model="blog.like" :blog="blog" :key="blog.id"></blog-post> </div> <script> Vue.component('blog-post', { model: { event: 'myevent', prop: 'liked' }, props: { blog: Object, liked: { type: Boolean, default: false } }, template: ` <div> <h3>{{blog.title}}</h3> <input type="checkbox" v-bind:checked="liked" v-on:click="likeClick"> </div> `, methods: { likeClick: function(event){ this.$emit('myevent',event.target.checked); } } }) Vue.createApp({ el: "#app", data: { blogs: [ {"title":"钢铁是怎样练成的?","id":1,"like":false}, {"title":"AI会毁灭人类吗?","id":2,"like":false}, {"title":"如何学好Vue!","id":3,"like":false}, ] }, created: function(){ // setInterval(()=>{ // this.blogs.forEach(blog => { // console.log(blog.title,blog.like); // }); // },2000); } }).mount("#app"); </script>

其中的props定义的两个属性分别是给外面调用组件的时候使用的。model总定义的prop:'like'是告诉后面使用v-model的时候,要修改哪个属性;event:'myevent'是告诉v-model,后面触发哪个事件的时候要修改属性。

插槽:

我们定义完一个组件后,可能在使用的时候还需要往这个组件中插入新的元素或者文本。这时候就可以使用插槽来实现。示例代码如下:

<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> ` }) Vue.createApp({ el: "#app" }).mount("#app"); </script>

当组件渲染的时候,<slot></slot>将会被替换为“个人中心”。插槽内可以包含任何模板代码,包括HTML

<navigation-link url="/profile"> <!-- 添加一个 Font Awesome 图标 --> <span class="fa fa-user"></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来进行绑定。示例代码如下:

<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> ` }) Vue.createApp({}).mount("#app"); </script>

590人已阅读,今天你学习了吗?

添加新回复