自定义组件
有时候有一组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
接收相同的选项,例如data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像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
默认会利用名为value
的prop(属性)
和名为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>