一、组件
(1)组件只有 导入并注册 后才能使用
(2)HTML对字母大小写不敏感。组件名、属性名使用短横线连接,就不要再用大写字母了。
1. 全局组件的注册
Vue.component(" 组件名称 ",{
data:组件数据,
template:组件模板内容
}
调用组件:<组件名称> </组件名称>
(1)组件中的 data 参数必须是 函数 ,同时这个函数要求返回一个对象。(因为函数可以形成闭包,保证每次组件调用时都有独立的数据。)
(2)组件模板 template 必须是 单个根元素。也就是所有模板内容都要放到一个大盒子内。
(3)组件模板 template 可以是 字符串,也可以是 ES6 的 模板字符串。(主要解决传统字符串不能随意换行的问题)
(4)组件可以在 HTML标签内调用(vue实例对象),也可以在其他组件的模板 template 中调用。
(5)组件命名时,可以用 短横线连接,也可以用 驼峰式 命名法。
(6)组件调用时,如果是在HTML标签内调用(vue实例对象),组件名必须 转化为 短横线连接;如果是在其他组件的 template 中调用,也可以用驼峰式。
(7)组件可以重复使用多次 。因为data中返回的是一个对象,所以 每个组件中的数据是私有的,即每个实例可以维护一份被返回对象的独立的拷贝。
(8)自定义组件的注册要放到 vue实例对象 之前。vue实例对象也是组件。
<div class="box">
<!-- 多次调用,数据独立 -->
<btn-count></btn-count>
<btn-count></btn-count>
</div>
<script src="./vue.js"></script>
<script>
// 组件的全局注册
Vue.component('btn-count', {
data: function () {
return { count: 0 };
},
// 使用模板字符串,并调用其他组件。
template: `<div>
<button v-on:click="handle">点击了{{count}}次</button>
<HelloWorld></HelloWorld>
</div>`,
methods: {
handle: function () {
this.count += 2;
},
},
});
// 使用驼峰式命名法和普通字符串定义组件
Vue.component('HelloWorld', {
data: function () {
return { msg: 'HelloWorld!' };
},
template: '<div>{{this.msg}}</div>',
});
// 创建vue实例对象
var vm = new Vue({
el: '.box',
data: {},
});
</script>
2. 局部组件的注册
components: {
HelloWorld:{
template: '<div>局部组件</div>',
},
}
(1)在vue实例对象中添加 components 属性。components 是一个对象,属性名是 组件名,属性值是 配置对象。
(2)和自定义局部指令、局部过滤器一样,局部组件只能在注册它的父组件内使用。
也就是,在vue实例对象中注册的局部组件只能在vue实例对象中使用。其他的全局组件不能使用。
<div class="box">
<!-- 调用局部组件 -->
<hello-world></hello-world>
</div>
<script src="./vue.js"></script>
<script>
var hw = {
template: '<div>局部组件</div>',
};
var vm = new Vue({
el: '.box',
// 注册局部组件
components: {
HelloWorld: hw,
},
});
</script>
3. 组件统一注册为全局组件(重点)
(1)想要把多个组件注册为全局可用的子组件,直接在 main.js 中用 Import导入,然后用 Vue.component( ) 注册,代码很多,不合适。
(2)这里,我们把导入和注册的代码单独封装到一个工具组件中。在main.js里,使用 Vue.use( )方法 执行工具组件中的代码。
(3)Vue.use( )方法接收一个配置对象,对象里面有一个 install( vue )函数,参数Vue是 Vue实例对象。
首先在工具组件Component中执行组件的全局注册
// 该文件负责所有的公共的组件的全局注册
// 1.导入组件
import PageTools from './PageTools'
export default {
install(Vue) {
// 2.全局注册组件
Vue.component('PageTools', PageTools)
}
}
然后在 main.js中调用工具组件Component,执行其中的代码。
import Component from '@/components'
Vue.use(Component) // 注册自己的插件
二、vue 调试工具的安装
三、组件之间的相互传值
1. 父组件向子组件传值 (自定义属性)
Vue.component("child",{
props:[ " title "],
template:" <div> {{ title } </div>
}
在父组件中调用:< child v-bind:title=" title "> </child>
① 父子组件的形成
- 通过路由的 children 属性可以形成 父子组件 的关系;
- 通过在父组件内 导入、注册、调用子组件也可以形成父子组件的关系;
- 父组件调用子组件时,可以用 <子组件名称 />的形式,还可以用路由中学的<component v-bind:is="子组件名称" />的方式调用子组件。
注意:使用 <component>标签时,不仅要注册子组件,还要把子组件的名字在data中定义。
② 父组件调用子组件时,通过给子组件 【添加自定义属性】 的形式传值。
③ 父组件传值时, 可以是静态的,也可以 用 v-bind绑定父组件的data数据。
④ 子组件在内部添加 【props】 属性来接收父组件传递的数据。props是一个 数组。
⑤ 这样就可以在子组件模板 template 内使用传递的数据了。
⑥ props 数组中接收的数据,可以是 数据名称的字符串,
⑦props 数组中接收的数据也可以是一个对象,规定每个数据的类型、是否必须等。
props: {
channel: {
//可选数据类型
type: [Object,Number,String],
//是否必要
required: true
//默认值, 数组和对象必须用下面的方式定义
default:function(){
return {}
}
//校验规则,防止传入数据不符合要求导致代码崩溃。
validator: function (value) {
return value >= 0
}
}
}
⑧ 在HTML标签中使用 属性名 时,必须转化为短横线连接;在 template 模板中可以使用驼峰式命名。
<div class="box">
<div>{{fmsg}}</div>
<!-- 在父组件中调用子组件 -->
<child v-bind:fmsg="fmsg" content="传递的静态内容"></child>
</div>
<script src="./vue.js"></script>
<script>
// 子组件
Vue.component('child', {
props: ["fmsg","content"],
data: function () {
return {
msg: '子组件本身的内容',
}
},
template: '<div>{{msg + "---" + fmsg + "---" + content}}</div>',
});
var vm = new Vue({
el: '.box',
data: { fmsg: '父组件的内容' },
});
</script>
⑨ 一个组件想要被 重用,某些数据就不要写在data中变成组件的私有数据,而是写在 props数组中,这样可以由父组件在调用时传入。
⑩ 父组件通过props传递给子组件的数据时异步执行的。比如父组件内点击表格中的某一行数据,可以把当前行的id传给弹出层子组件,但是弹出层子组件不能用props中的id发起Ajax请求获取数据,而是应该在子组件封装获取数据的方法,在父组件中调用子组件方法。
父组件渲染页面时,执行到子组件占位符时就会渲染子组件,不论子组件是否显示在页面上。但是此时props传递的数据还没传递给子组件。
this.$nextTick(() => { }) 是vue提供的方法,他会等待DOM元素更新之后再去执行后面的操作。
vue中数据和dom渲染是异步的(数据驱动视图是异步的),所以,要让dom结构随数据改变这样的操作,都应该放进 this.$nextTick() 的回调函数中。
created()中使用方法时,dom还没有渲染,如果此时在该钩子函数中进行dom赋值数据(或者其它dom操作),无异于徒劳,所以,此时this.$nextTick()就会被大量使用,而与created()对应的是mounted()的钩子函数则是在dom完全渲染后才开始渲染数据,所以在mounted()中操作dom基本不会存在渲染问题。
2. 组件之间传递的值的类型
① 字符串、数字型、布尔型、数组、对象。
② 子组件被调用时,父组件传递的 数字型 和 布尔型的值,如果没有绑定 v-bind,则传递的是 字符串 类型。
<div class="box">
<!-- 调用子组件,传递五种类型的值 -->
<child :str="str" :num="10" :boolen="true" :arr="arr" :obj="obj"></child>
</div>
<script src="./vue.js"></script>
<script>
// 子组件
Vue.component('child', {
// 子组件接收传递过来的五种值
props: ['str', 'num', 'boolen', 'arr', 'obj'],
// 子组件在模板中使用传递过来的值
template: `<div>
<div>{{str}}</div>
<div>{{num}}</div>
<div>{{boolen}}</div>
<ul>
<li v-for="item in arr">{{item}}</li>
</ul>
<ul>
<li v-for="(v,k,i) in obj">{{v + k}}</li>
</ul>
</div>`,
});
var vm = new Vue({
el: '.box',
data: {
str: '你好',
arr: ['吴磊', '王祖贤', '吴彦祖'],
obj: {
name: '吴磊',
gender: '男',
},
},
});
</script>
3. 子组件向父组件传值 (自定义事件)
Vue.component('child', {
template:
"<div>
<button v-on:click='$emit("change",5)'>子组件按钮</button>
</div>",
});
调用子组件:<child v-on:change="handle($event)"></child>
① 子组件的 props 属性使用原则:单向数据流。即在子组件中修改数据不会同步到父组件,而子组件的数据来自父组件,父组件的数据一旦发生变化,子组件的数据就白修改了。
② 具体来说:不要在子组件内修改数字型、布尔型、字符串数据,不能对数组、对象重新赋值,但是可以修改数组和对象的元素与属性。
③ 父组件在调用子组件时,给子组件注册 自定义事件,并在methods中声明事件的处理函数。
④ 在子组件的 template 模板中,用 this.$emit 触发自定义事件。(可以直接在template模板中绑定,也可以在methods中绑定。)事件的第一个参数是 事件名称,第二个参数是 想要传递的值。
⑤ 如果传递多个值,可以用 对象 的形式。并且可以传递 事件对象$event。
⑥ 父组件通过 $event 获取到子组件传递的值。
<div class="box">
<div v-bind:style="{fontSize: fontSize +'px'}">{{msg}}</div>
<!-- 在父组件中调用子组件 -->
<child v-on:change="handle($event)"></child>
</div>
<script src="./vue.js"></script>
<script>
// 子组件,直接在template模板中绑定$emit事件
Vue.component('child', {
template: `<div>
<button v-on:click='$emit("change",5)'>子组件按钮</button>
</div>`,
});
var vm = new Vue({
el: '.box',
data: { msg: '父组件的内容', fontSize: 16 },
methods: {
handle: function (val) {
this.fontSize += val;
},
},
});
</script>
4. 子组件对父组件的数据 【使用并修改】
如果子组件既要使用父组件中的数据,又要修改父组件的数据。
(1) 传统做法:
① 父组件调用子组件时,用v-bind绑定数据进行传递;子组件在props属性中接收。
② 子组件修改数据时,用$emit绑定方法;父组件监听此方法,并在事件函数中修改数据。
(2) 新方法:v-model(vue3.0有变化!!!)
① 父组件调用子组件时,用 v-model 绑定数据进行传递;子组件在props属性中接收。
<FollowUser v-model="fatherData" ></FollowUser>
② 子组件接收数据时,数据名称固定为 value。
③ 子组件修改数据时,用$emit触发自定义事件,事件名称固定为 input。父组件无需注册和声明此事件。
④ 如果子组件想要修改数据名称和事件名称,则在组件内添加 model属性。
model: {
prop: 'data', //默认名称为value
event: 'changeData' //默认名称为input
},
props:[ 'data' ],
methods:{
handler( ){ this.$emit('changeData',newData)}
}
注意:v-model绑定数据只能用一次。
(3)新方法:.sync(vue3.0已删除!!!)
① 父组件传递数据时,在数据名称后面添加 .sync修饰符。
<child :dialog-visible.sync="dialogVisible" />
② 此时,子组件就可以按照下面的格式修改这个数据,而父组件不用注册$emit的事件。
this.$emit('update:dialogVisible', false)
update: 是固定写法,参数false是修改的值。
(4)子组件中 this.$parent 可以获取到父组件的实例对象。
(5)vue3.0 v-model
父组件调用子组件时,用 v-model 绑定数据,子组件用 prop接收数据,
用 this.$emit("update:数据名",最新值) 进行修改。
5.父组件向后代子组件传值
通过 依赖注入 ,父组件声明数据,后代子组件接收数据并使用。当某个父组件的数据被多个子组件使用时使用这种方法。
① 父组件声明数据:
provide: function () {
return {
articleID: this.articleID
}
},
② 后代子组件接收数据
inject: {
articleID: {
type: [Number, String, Object],
default: null
}
}
6. 兄弟组件传值(了解)
① 定义一个 事件中心。
var hub = new Vue()
② 在接收数据的组件内,添加 mounted钩子函数 ,在mounted函数内书写 监听事件的函数。监听的事件处理的是本组件的数据。
注意: 这里使用箭头函数是因为 this 指向的问题。
mounted: function() {
hub.$on( '自定义事件名', (形参) => { });
}
③ 在传递数据的组件内,为HTML标签绑定事件 hub.$emit( '自定义事件名',传递的数据)。触发的是兄弟组件的事件。
handle: function(){
hub.$emit( '自定义事件名',实参)
}
④ 在父组件内,给html元素绑定事件,通过 hub.$off( "自定义事件名") 销毁事件。
<button v-on:click="handle">销毁事件</button>
handle: function () {
hub.$off('自定义事件名');
}
四、组件插槽
1. 组件插槽基本用法
通过组件插槽,可以在调用子组件时,向标签内填写内容。
① 在子组件的 template 模板中添加一个固定的标签 <slot> </slot>作为插槽。
② 在父组件中调用子组件时,可以向标签内填写内容,标签中的内容被传递到子组件插槽中,显示到页面。
③ 在父组件中调用子组件时,如果向标签内添加内容,则<slot></slot>标签内的默认内容会被覆盖。
<div class="box">
<!-- 调用子组件 -->
<child>你好!</child>
</div>
<script src="./vue.js"></script>
<script>
// 子组件
Vue.component('child', {
template: `<div>
<strong>错误提示:</strong>
<slot>默认内容</slot>
</div>`,
});
// vue实例对象--父组件
var vm = new Vue({
el: '.box',
data: {},
});
</script>
2.具名插槽 (重点)
具名插槽的两种使用情况:
(1)在项目中,想要给组件库中的组件,添加一些 自定义内容,组件本身会提供相应的 具名插槽 ,我们只需要使用插槽填充内容即可。
(2)对于自己写的组件,我们需要在子组件预留插槽位置,然后父组件调用时给插槽填充内容。
① 在子组件的 template 模板中,可以给 插槽<slot>标签 添加名字。
<slot name="名字"> </slot>
② 在父组件中调用子组件时,可以在标签内部 添加任意标签,并给这些标签添加slot名字后,标签内的内容就会被传递给子组件对应的具名插槽。
<child>
<p slot="名字"> 传递的内容 </p>
</child>
③ 如果想给一个具名插槽添加 多个标签的内容 的话,使用 <template>标签 包裹。
<child>
<template slot="名字">
<p> 传递的内容1 </p>
<span>传递内容2</span>
</template>
</child>
如果使用 template 包裹,则使用插槽时,还可以用 v-slot:名称 的形式。
<template v-slot:"名字">
</template>
或者直接用 # 插槽名称
<div class="box">
<!-- 调用子组件,给插槽传递内容 -->
<child>
<p slot="header">放在header插槽中的内容</p>
<p>放在无名插槽中的内容</p>
<p slot="footer">放在footer插槽中的内容</p>
</child>
<template slot="名字">
<p> 传递的内容1 </p>
<span>传递内容2</span>
</template>
<!-- 调用子组件,给每个插槽传递多个内容 -->
<child>
<template slot="header">
<p>放在header插槽中的内容1</p>
<p>放在header插槽中的内容2</p>
</template>
<template>
<p>放在无名插槽中的内容1</p>
<p>放在无名插槽中的内容2</p>
</template>
<template slot="footer">
<p>放在footer插槽中的内容1</p>
<p>放在footer插槽中的内容2</p>
</template>
</child>
</div>
<script src="./vue.js"></script>
<script>
// 子组件
Vue.component('child', {
template: `<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>`,
});
// vue实例对象--父组件
var vm = new Vue({
el: '.box',
data: {},
});
</script>
3. 作用域插槽 (slot-scope)
- 父组件获取到子组件的数据,并加工处理。
- 当我们希望子组件的样式由外部使用组件的地方定义,因为可能有多种地方要使用该组件,但样式希望不一样 这个时候我们需要使用作用域插槽。
① 父组件通过 绑定属性 向子组件传递数据。
② 子组件在 template 模板中使用数据时,提供一个 作用域插槽。并给插槽绑定自定义属性。属性名是自定义的 info,属性值时要操作的数据 item。
<li :key='item.id' v-for='item in list'>
<slot v-bind:info='item'>{{item.name}}</slot>
</li>
③ 父组件调用子组件时,在标签内添加 <template slot-scope='自定义名称'>标签。
并添加 slot-scope属性,属性值是 自定义的 slotProps。通过 slotProps.info可以获得子组件的数据item。
<template slot-scope='slotProps'>
<strong v-if='slotProps.info.id==3' class="current">
{{slotProps.info.name}}
</strong>
</template>
<div class="box">
<!-- 调用子组件 -->
<child v-bind:star="star">
<!-- template模板 -->
<template slot-scope="slotProps">
<span v-if="slotProps.info.id==2" class="current"
>{{slotProps.info.name}}</span
>
<span v-else>{{slotProps.info.name}}</span>
</template>
</child>
</div>
<script src="./vue.js"></script>
<script>
// 子组件
Vue.component('child', {
props: ['star'],
template: `<div>
<ul>
<li v-for="item in star">
<slot v-bind:info="item">{{item.name}}</slot>
</li>
</ul>
</div>`,
});
// vue实例对象--父组件
var vm = new Vue({
el: '.box',
data: {
star: [
{ id: 1, name: '吴磊' },
{ id: 2, name: '王祖贤' },
{ id: 3, name: '吴彦祖' },
],
},
});
</script>
4. 项目中使用作用域插槽 (重点)
(1)使用场景:
和具名插槽一样,如果使用的是组件库提供的组件,它内部已经预留好了作用域插槽的位置,我们只需要使用插槽填充内容即可。
(2)使用说明:
在项目中,当 v-for循环数据 进行渲染页面时(某些组件默认具有循环渲染的功能),有些地方不是直接把数据丢到标签里,就能渲染出我们想要的样式,此时,我们就添加一个 <template> 标签作为对作用域插槽的调用。<template>标签既可以自定义HTML结构,也可以获取到 当前循环项 的数据。
(3)例如:
① ElementUI组件库中提供的 表格组件,给table表格绑定了 v-bind:data 属性后,就可以使用 data数组 中的数据进行渲染HTML页面了。
② 但是数据中的 level 是一个数字,而我们想要的效果是一个tag按钮。此时,可以使用<template>标签绑定作用域插槽 slot-scope="scope",其最主要的作用是:可以通过 scope.row 拿到当前循环项的数据对象。
<el-table :data="rightsList" border stripe>
<el-table-column type="index" ></el-table-column>
<el-table-column label="" prop="authName"></el-table-column>
<el-table-column label="" prop="path"></el-table-column>
<el-table-column label="" prop="level">
<template slot-scope="scope">
<div>{{scope.row}}</div>
</template>
</el-table-column>
</el-table>
③ 也可以在调用插槽时绑定解构后的数据,如 slot-scope="{row}",此时,row就是当前循环项的数据对象。
总结
不使用组件库的组件时,我们给标签添加 v-for="(item,index) in array,来循环渲染数据。此时,可以通过 item 很方便地获取到 每个循环项的信息。
使用了组件库提供的组件模板后,很多组件会默认具有循环渲染的功能,不需要我们用 v-for 绑定标签。此时,我们就借助 作用域插槽模板 ,来获取 每个循环项的信息。
父组件向子组件传值,或者从vuex中映射过来的数据和方法,
在代码中 都要加 this 使用。用 import 导入的方法则不需要用 this 。
而在template模板中使用数据都 不能加this。js 文件必须导入 vuex 模块后才能使用其中的数据。组件可以直接 this.$store调用、或是映射vuex中的数据和方法。
组件模板中的数据只能使用data 或 父组件传递的 props 或 路由规则传递的静态参数。如果用import 导入了一些数据,必须先引入到data中,才能在模板中使用。
单文件组件,可以根据 路由 形成组件的父子关系。路由链接、路由占位符、路由组件、路由规则 必不可少。
也可以直接在父组件内 导入、注册、调用 子组件。组件的重用:父组件调用子组件,传递一些必要数据;子组件接收数据,根据这些数据,在组件内部发起Ajax请求,获取渲染页面所需要的数据,并渲染页面。
由于每个父组件重用子组件时传递的必要数据不相同,所以渲染出来的子组件也不同。ref可以获取到元素的DOM对象;如果ref作用在 组件上(自定义组件或者组件库的组件)时,可以得到该组件的 实例,也就是 this