VUE自定义组件
简介
有时候有一组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>

浙公网安备 33010602011771号