一、组件的概念及复用
1.1 为什么要使用组件
组件(component)是vue.js最核心的功能,用来实现局部(特定)功能效果的代码集合(html/css/js/image…..)。
组件是可复用的 Vue 实例, 把一些公共的模块抽取出来,然后写成单独的的工具组件或者页面,在需要的页面中就直接引入即可那么我们可以将其抽出为一个组件进行复用。例如页面头部、侧边、内容区,尾部,上传图片,等多个页面要用到一样的就可以做成组件,提高了代码的复用率。
非单文件组件:一个文件中包含多个组件。(真正开发中几乎不用)
单文件组件:一个文件中只包含1个组件。
单文件组件由一个xxx.vue 文件的组成(3 个部分):
1. 模板页面
<template>
页面模板
</template>
2. JS 模块对象
<script>
export default {
data() {return {}}, methods: {}, computed: {}, components: {}
}
</script>
3. 样式
<style>
样式定义
</style>
二、使用props传递数据
2.1 基本用法
组件不仅仅是把模板的内容进行复用,更重要的是组件间的通信,通常父组件的模板中包含子组件,父组件要正向地向子组件传递数据以及参数,子组件接收到参数后再根据参数的不同来渲染不同的内容或执行操作。这个正向传递数据的过程就是通过props来实现的。
父组件:vue-cli创建的项目结构中的App.vue为组件之根(父组件)
通过列表页(父组件)与列表项目组件(子组件)为例子,演示props的使用。
父组件示例代码(App.vue):
<template>
<div id="app">
<h1>这是列表页</h1>
<!-- 3.组件调用,并向子组件传递了参数message -->
<ListItem message="来自列表页的数据"></ListItem>
</div>
</template>
<script>
//1.引入子组件ListItem
import ListItem from '@/components/ListItem'
export default {
name: 'App',
data(){
return{
}
},
//2.注册子组件ListItem
components:{ListItem}
}
</script>
<style>
</style>
子组件(列表项目组件)接收父组件传递过来的数据,子组件列表项目代码(ListItem.vue):
<template>
<div class="list">
<ul>
<li>这是第一条数据</li>
</ul>
<!-- 在页面中把父组件传递过来的数据显示出来 -->
<p>{{message}}</p>
<p>{{msg}}</p>
</div>
</template>
<script>
export default {
name:'ListItem',
data(){
return{
msg:'我是组件本身的数据'
}
},
//通过props接收父组件传递过来的数据
props:["message"]
}
</script>
<style scoped>
.list{
color:#f00;
}
.list p{
font-weight: 700;
}
</style>
?props中声明的数据与组件data函数返回的数据的主要区别:
- props中声明的数据来自父级,而data中是组件自己的数据,作用域是组件本身;
- 这两种数据都可以在模板(template)及方法(method)中使用;
?使用v-bind指令动态绑定props值:
父组件示例代码(App.vue):
<template>
<div id="app">
<h1>这是列表页</h1>
<!-- 3.组件调用,并向子组件动态传递了参数message -->
<button @click="change()">改变数据</button>
<ListItem :message="listdata"></ListItem>
</div>
</template>
<script>
//1.引入子组件ListItem
import ListItem from '@/components/ListItem'
export default {
name: 'App',
data(){
return{
listdata:'原始数据'
}
},
methods:{
change(){
this.listdata='单击按钮之后的数据'
}
},
//2.注册子组件ListItem
components:{ListItem}
}
</script>
<style>
</style>
子组件(列表项目组件)接收父组件动态传递过来的数据,子组件列表项目代码(ListItem.vue)不变。
?初始状态下,listdata设置的字符串为“原始数据”,如左图,单击改变数据的按钮之后,listdata值改变为“单击按钮之后的数据”,如右图所示。
2.2 单向数据流
上一节我们讲到父组件数据变化可以传递给子组件,但是反过来是不成立的,所以props传递数据是单向的。之所以这么设计,就是尽可能将父子组件解耦,避免子组件无意间修改父组件的状态。
业务中经常会遇到两种需要改变props传递过来数据的情况:
父组件传递初始值,子组件将其初始值保存起来,再自己作用域内可以随意使用和更改,这个时候可以在子组件的data中再声明一个数据来引用父组件传递过来的数据。
父组件代码示例:(App.vue)
<template>
<div id="app">
<h1>这是列表页</h1>
<!-- 3.组件调用,并向子组件动态传递了参数message -->
<ListItem :message="count"></ListItem>
</div>
</template>
<script>
//1.引入子组件ListItem
import ListItem from '@/components/ListItem'
export default {
name: 'App',
data(){
return{
count:'100'
}
},
//2.注册子组件ListItem
components:{ListItem}
}
</script>
<style>
</style>
子组件代码: (ListItem.vue)
<template>
<div class="list">
<ul>
<li>这是第一条数据</li>
</ul>
<!-- 在页面中把父组件传递过来的数据显示出来 -->
<p>{{receive}}</p>
<p style="color:green">{{msg}}</p>
</div>
</template>
<script>
export default {
name:'ListItem',
data(){
return{
msg:'我是组件本身的数据',
receive:this.message
}
},
//通过props接收父组件传递过来的数据
props:["message"]
}
</script>
<style scoped>
.list{
color:#f00;
}
.list p{
font-weight: 700;
}
</style>
子组件中声明了receive,在组件初始化的时候就会获得来自父组件的message,之后就与message无关了,只需要维护receive,这样就避免了直接操作message。
三、组件通信
通过前面的学习,我们已经知道父组件与子组件通信通过props传递数据就可以,但是vue组件通信的场景远不止这一种,最容易想到的还有子组件传给父组件。组件之间传值可以用下图表示:
?组件通信流程图
3.1 自定义事件及$emit方法
Vue.js允许正向传值,也就是父组件传值给子组件。正向传值不需要条件触发,是主动的,逆向传值则是不允许的,需要主动触发,需要主动抛出自定义事件去监听。
语法:
this.$emit('event',val)
$emit为实例方法,用来触发事件监听。其中,参数event代表自定义事件名称,参数val代表通过自定义事件传递的值,注意这里的val为可选参数。
示例代码:(父组件App.vue)
<template>
<div id="app">
<h1>父组件</h1>
<p>子组件传递的数据:{{message}}</p>
<!-- 通过父组件给子组件绑定一个自定义事件change,实现子给父传数据 -->
<ListItem @change='getVal'></ListItem>
</div>
</template>
<script>
//1.引入子组件ListItem
import ListItem from '@/components/ListItem'
export default {
name: 'App',
data(){
return{
message:""
}
},
methods:{
getVal(val){
this.message=val
}
},
//2.注册子组件ListItem
components:{ListItem}
}
</script>
<style>
</style>
示例代码:(子组件ListItem.vue)
<template>
<div class="list">
<h1>子组件</h1>
<button @click="fn()">单击子组件传值父组件</button>
</div>
</template>
<script>
export default {
name:'ListItem',
data(){
return{
cMsg:"子组件要传递给父组件的数据信息"
}
},
methods:{
fn(){
this.$emit("change",this.cMsg)
}
}
}
</script>
<style scoped>
</style>
?
??? emit初始显示状态???????????????????????????????????????????? 单击按钮之后传值父组
3.2 兄弟组件通信的处理方式
兄弟组件之间的传值最容易想到的解决方案是通过共同的父组件进行中转,这里假设一个场景,组件1中的某个数量需要在组件2中同步展示,这个时候就会涉及到兄弟组件之间的传值。通过一个案例来看一下具体代码的实现方式:
示例代码:(App.vue)
<template>
<div id="app">
<h1>父组件</h1>
<p>{{message}}</p>
<child-one @change='getVal'></child-one>
<child-two :count="message"></child-two>
</div>
</template>
<script>
//1.引入子组件ListItem
import ChildOne from './components/ChildOne.vue'
import ChildTwo from './components/ChildTwo.vue'
export default {
name: 'App',
data(){
return{
message:0
}
},
methods:{
getVal(val){
this.message=val
}
},
//2.注册子组件ListItem
components:{ChildOne,ChildTwo}
}
</script>
<style>
</style>
示例代码:(ChildOne.vue)
<template>
<div class="one">
<h3>子组件</h3>
<button @click="fn">传值到父组件</button>
</div>
</template>
<script>
export default {
name:'ChildOne',
data(){
return{
num:'10'
}
},
methods:{
fn(){
this.$emit('change',this.num);
}
}
}
</script>
<style>
.one{
border: 1px solid #000;
padding: 10px;
margin-bottom: 10px;
}
</style>
示例代码:(ChildTwo.vue)
<template>
<div class="two">
<h3>子组件2</h3>
<P>{{count}}</P>
</div>
</template>
<script>
export default {
name:'ChildTwo',
data(){
return{
}
},
props:["count"]
}
</script>
<style>
.two{
border: 1px solid #000;
padding: 10px;
margin-bottom: 10px;
}
</style>
兄弟组件传值初始显示
?兄弟组件传值之后显示
四、slot分发内容
4.1 什么是slot
slot的官方定义是用于组件内容分发,slot还有一个形象的名字“插槽”。简单通俗的解释就是在组件化开发中,虽然组件是一样的,但是在不同的使用场景,组件的某一部分需要有不同的内容显示。
4.2 匿名slot
匿名slot从字面理解就是没有名字的插槽,特点是可以放任何内容。
假设场景:设想一个弹出提示框的场景,提示框都包括头部、中间内容和底部内容三部分,头部和底部都是固定不变的,改变的知识中间内容,所以我们在定义组件时可以将中间内容定义为插槽。
示例代码:(子组件popup.vue)
<template>
<div>
<p>头部区域</p>
<!-- 在变化内容区域定义一个插槽 -->
<slot>如果没有分发内容,则显示默认提示</slot>
<p>底部区域</p>
</div>
</template>
<script>
export default {
data(){
return{
}
}
}
</script>
<style>
</style>
示例代码:(父组件App.vue)
<template>
<div id="app">
<popup>
<!-- 插槽显示的具体内容 -->
<h1>显示弹出框内容</h1>
</popup>
</div>
</template>
<script>
//1.引入子组件popup
import Popup from './components/popup.vue'
export default {
name: 'App',
data(){
return{
message:0
}
},
methods:{
},
//2.注册子组件ListItem
components:{Popup}
}
</script>
<style>
</style>
?4.3 具名slot
具名slot可以用一个特殊属性name来配置如何分发内容,多个slot可以有不同的名字,具名slot将匹配内容片段中有对应slot特性的元素。
代码示例:(computer.vue子组件)
<template>
<div>
<slot name="CPU">这儿是CPU插槽</slot>
<slot name="GPU">这儿是显卡插槽</slot>
<slot name="Memory">这儿是内存插槽</slot>
<slot name="Hard-drive">这儿是硬盘插槽</slot>
</div>
</template>
<script>
export default {
data(){
return{
}
},
components:{}
}
</script>
<style>
</style>
?代码示例:(App.vue父组件)
<template>
<div id="app">
<computer>
<!-- 插槽显示的具体内容 -->
<div slot="CPU">Initel Core i7</div>
<div slot="GPU">GTX980Ti</div>
<div slot="Memory">Kingston 32G</div>
<div slot="Hard-drive">Samsung SSD 1T</div>
</computer>
</div>
</template>
<script>
//1.引入子组件computer
import Computer from './components/computer.vue'
export default {
name: 'App',
data(){
return{
message:0
}
},
methods:{
},
//2.注册子组件computer
components:{Computer}
}
</script>
<style>
</style>
?
?
|