文章目录
- 3.1传统方式编写和组件化方式编写区别?
- 3.2概念:模块与组件、模块化与组件化
- 3.3非单文件组件
- 3.4单文件组件
- 3.5定义组件
- 3.6 使用组件
- 3.7 is 属性
- 3.8模板
- 3.9data属性
- 3.10props属性
- 3.11props校验
- 3.12非props属性
- 3.13自定义事件
- 3.14插槽分发
- 3.15.动态组件
- 3.16 refs属性
- 3.17.生命周期
- 3.18混入
- 3.19插件
- 3.20 标签属性scoped用于控制样式
- 3.21浏览器本地缓存
- 3.22全局事件总线(GlobalEventBus)
- 3.23消息订阅与发布
- 3.24 nextTick语法
- 3.25Vue封装的过度与动画
- 3.26.vm调用待$命令介绍
- 本人其他相关文章链接
3.1传统方式编写和组件化方式编写区别?
传统方式编写
组件化方式编写
3.2概念:模块与组件、模块化与组件化
1)模块
2)组件
3)模块化
4)组件化
3.3非单文件组件
问题:“单文件组件”与“非单文件组件”区别?
3.3.1使用Vue.extend({})方式创建组件(了解,实际不常用)
Vue.extend({})属于vue的全局API,在实际业务开发中很少使用,因为相比较vue.component来说,使用Vue.extend({})更加繁琐,但在一些独立组件开发场景中,Vue.extend+$mount是个不错的选择。
注意点1:Vue.extend的配置参数中不可以使用el绑定挂在id,跟创建vm实例不同
注意点2:组件使用方式3步:
- 第一步:创建组件
- 第二步:注册主键
- 第三步:使用组件
注意点3:注册局部组件可以写简写方式
普通方式:K,V形式,其中前面的K才是主键真实名字,后面V名字不起效果,当然为了方便辨认,创建的名字K,V尽量保持一致即可
components:{
'hello': 'hello':
}
简写方式:只保留K即可,省略V
components:{
'hello'
}
注意点4:组件名由一个单词组成小写hello,实际在Vue开发者工具中会首字母大写Hello,组件名由多个单词组成,实际在Vue开发者工具中会多个单词首字母都大写,当然只是个展示样式而已。
注意点5:定义组件简写形式const school = Vue.extend(options) 可简写为:const school = options,说明定义组件时未绑定Vue.extend,那么vue会默认包裹一层Vue.extend,如果定义组件绑定了Vue.extend,那么vue就会跳过包裹步骤。
举例:
<div id="root">
<!-- 第三步:使用组件标签 -->
<hello></hello>
<hr>
<h1>{{msg}}</h1>
</div>
<script type="text/javascript">
//第一步:创建hello组件
const hello = Vue.extend({
template:`
<div>
<h2>你好啊!{{name}}</h2>
</div>
`,
data(){
return {
name:'Tom'
}
}
})
//第二步:全局注册组件
Vue.component('hello',hello)
//创建vm
new Vue({
el:'#root',
data:{
msg:'你好啊!'
},
//第二步:注册组件(局部注册)
components:{
'hello'
}
})
3.3.2组件嵌套
举例:练习组件嵌套
div id="root">
</div>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
//定义student组件
const student = Vue.extend({
name:'student',
template:`
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return {
name:'尚硅谷',
age:18
}
}
})
//定义school组件
const school = Vue.extend({
name:'school',
template:`
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div>
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
},
//注册组件(局部)
components:{
student
}
})
//定义hello组件
const hello = Vue.extend({
template:`<h1>{{msg}}</h1>`,
data(){
return {
msg:'欢迎来到尚硅谷学习!'
}
}
})
//定义app组件
const app = Vue.extend({
template:`
<div>
<hello></hello>
<school></school>
</div>
`,
components:{
school,
hello
}
})
//创建vm
new Vue({
template:'<app></app>',
el:'#root',
//注册组件(局部)
components:{app}
})
</script>
3.3.3VueComponent构造函数
注意点1:vm管理一堆的vc
注意点2:
问题:vc和vm区别? vc≠vm
答案:
相同点:属性和方法99%相同
不同点:
1)vm可以绑定el决定为哪个容器服务,而vc不可以,vc只能被vm管理
2)vm的data可定义对象形式,可定义函数形式,而vc中data只能定义函数形式
注意点3:“组件实例对象” =》 简称vc
问题:“组件实例对象”是啥呢?
答案:就是小型的vm
<body>
<div id="root">
<school></school>
<hello></hello>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
//定义school组件
const school = Vue.extend({
name:'school',
template:`
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
},
methods: {
showName(){
console.log('showName',this)
}
},
})
const test = Vue.extend({
template:`<span>atguigu</span>`
})
//定义hello组件
const hello = Vue.extend({
template:`
<div>
<h2>{{msg}}</h2>
<test></test>
</div>
`,
data(){
return {
msg:'你好啊!'
}
},
components:{test}
})
// console.log('@',school)
// console.log('#',hello)
//创建vm
const vm = new Vue({
el:'#root',
components:{school,hello}
})
</script>
3.3.4一个重要的内置关系
注意点1:
Demo.prototype =》 指代Demo函数上面的“显示原型属性”
d.__proto__ => 指代实例d上面的“隐式原型属性”
无论是“Demo函数上面的显示原型属性”还是“实例d上面的隐式原型属性”都指向同一个对象,它叫“原型对象”
注意点2(重要):
一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
注意点3(重要):一句话:实例的隐式原型属性永远指向自己缔造者的原型对象
注意点4:下面图将展示注意点2的关系,即“让组件实例对象(vc)可以访问到 Vue原型上的属性、方法”。
3.4单文件组件
3.5定义组件
Vue 自定义组件分为两种:全局注册和局部注册,全局组件可以在任何地方引用,局部
组件只能在当前 Vue 实例使用。
3.5.1 全局注册
组件
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。
1、定义组件
Vue自定义组件分为两种:全局注册和局部注册,全局组件可以在任何地方引用,局部组件只能在当前Vue实例使用。
1)全局注册
使用Vue.component(tagName, options)来定义:
Vue.component('hello',{
template:"<h1>enjoy your life!</h1>"
})
2)局部注册
在Vue实例中使用components属性来定义:
new Vue({
el:"#app",
components:{
"inner-hello":{
template:"<h2>我是局部组件</h2>"
}
}
});
注意点1:HTML 特性是不区分大小写的,所有在定义组件时尽量使用中划线“-”来指定组件名。
即使,使用了驼峰标示命名如:myComponent,在页面引用时仍然要使用进行引用。
使用 Vue.component(tagName, options)来定义:
<body>
<div id="root">
<!--使用组件-->
<hello></hello> <!--必须放在是实例化组件中才能生效,普通div无效-->
<hello></hello>
</div>
<div id="app">
<hello></hello>
<inner-hello></inner-hello>
</div>
</body>
<script type="text/javascript">
/*定义全局组件*/
Vue.component('hello',{
template:"<h1>enjoy your life!</h1>"
})
new Vue({
el:"#app",
components:{
"inner-hello":{
template:"<h2>我是局部组件</h2>"
}
}
});
new Vue({
el:"#root",
data:{
}
})
</script>
3.5.2 局部注册
在 Vue 实例中使用 components 属性来定义:
3.6 使用组件
3.7 is 属性
问题:为什么在table标签中直接使用自定义组件,无法正常显示?
原因:
DOM解析时会解析到<table>标签的外部,table/ol/ul/select 这种html标签有特殊的结构要求,不能直接使用自定义标签。他们有自己的默认嵌套规则,比如:
table> tr> [th, td];
ol/ul > li;
select > option
<body>
<div id="app">
<table>
<tr is="hello"></tr><!--在tr中+is,相当于在tr中加入一个全局组件,而且认同它-->
</table>
</div>
</body>
<script type="text/javascript">
/*定义全局组件*/
Vue.component('hello',{
template:"<h1>你愁啥!</h1>"
})
new Vue({
el:"#app",
data:{
}
})
</script>
3.8模板
当模板的 html 结构比较复杂时,直接在 template 属性中定义就不现实了,效率也会很低,此时我们可以使用模板,定义模板的四种形式:
问题:什么叫在使用字符串模板、x-template模板和.Vue组件时,不需要is进行转义?
答案:不需要转义如图1,需要转义如图2(详情请看知识点导航3.4.4)
<script type="text/x-template" id="template5">
<table>
<my-component1></my-component1>
</table>
</script>
Vue.component('my-component2',{
template:'#template2'
});
<table>
<tr is="my-component3">
</table>
<template id="template3">
<ol>
<li>a</li>
<li>b</li>
</ol>
</template>
Vue.component('my-component3',{
template:'#template3'
});
3.8.1直接字符串
var temp = '<h4>直接字符串</h4>';
Vue.component('my-component1',{
template:temp
});
3.8.2 x-template模板
<!-- 使用x-template -->
<script type="text/x-template" id="template2">
<ul>
<li>01</li>
<li>02</li>
</ul>
</script>
Vue.component('my-component2',{
template:'#template2'
});
3.8.3 template标签
<!-- 使用template标签 -->
<template id="template3">
<ol>
<li>a</li>
<li>b</li>
</ol>
</template>
Vue.component('my-component3',{
template:'#template3'
});
3.8.4 省略is
<!-- 使用x-template -->
<script type="text/x-template" id="template5">
<table>
<my-component1></my-component1>
</table>
</script>
Vue.component('my-component6',{
template:'#template5'
});
正常<table><ol><ul><select>等都不能直接使用自定义组件标签,除非指定is属性,而在4种实现模板方式中除了<template>模板,其他3种如字符串模板和x-template模板和.vue组件时中可以直接嵌套自定义组件标签,不用is指定。
3.9data属性
通过data属性指定自定义组件的初始数据,要求data必须是一个函数,如果不是函数就会报错。
Vue.component('my-component',{
template:'<button @click="count += 1">计数{{count}}</button>',
data: function () {
return{count:0}
}
});
综合例子:
<div id="app">
<my-component></my-component>
<my-component></my-component>
<my-component></my-component>
</div>
<script>
/***
* 定义组件内部data: 必须通过函数定义
* */
Vue.component('my-component',{
template:'<button @click="count += 1">计数{{count}}</button>',
data: function () {
return{count:0}
}
});
var app = new Vue({
el:'#app',
data:{
count:0
},
});
</script>
结果
3.10props属性
组件可以嵌套使用,叫做父子组件。那么父组件经常要给子组件传递数据这叫做父子组件通信。父子组件的关系可以总结为 props 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。看看它们是怎么工作的:
使用父组件给子组件传递属性流程:
- 在父组件中定义数据
- 在使用组件时,绑定父组件中的数据
- 在子组件中通过props属性声明父组件中传递过来的参数
- 在template属性中使用父组件中的参数
举例:父组件给子组件传递属性msg和greetText,子组件用属性a和b接收,并打印输出
<div id="app">
<!-- v-bind:a 简写成 :a -->
<child :a="msg" :b="greetText"></child>
</div>
<script>
/***
* 父子组件通信: 父组件通过props属性向子组件进行数据传递
* 使用方式: 子组件定义时用props指定接收参数名
* */
Vue.component('child', {
//声明props
props:['a','b'],
//使用父组件传递的数据
template:'<span>{{a}} == {{b}}</span>'
});
var app = new Vue({
el:'#app',
data:{
msg:'来自父组件的消息',
greetText:'你好Child'
}
});
</script>
结果展示
3.11props校验
子组件在接收父组件传入数据时, 可以进行prop校验,来确保数据的格式和是否必传。
注意点1:props可配置3种形式,如果没有参数格式化校验,推荐形式1使用居多。
形式1:简单声明接收
props:['name','age','sex']
形式2:接收的同时对数据进行类型限制
props:{
name:String,
age:Number,
sex:String
}
形式3:接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
props:{
name:{
type:String, //name的类型是字符串
required:true, //name是必要的
},
age:{
type:Number,
default:99 //默认值
},
sex:{
type:String,
required:true
}
}
注意点2:
问题:如果data中属性名和props中属性重名,谁优先级高?
答案:props种属性名优先级高,因此为了避免不必要问题,避免data中属性名和props中属性重名
注意点3:props后面是对象而不是数组形式(即:只接收属性不校验可使用数组形式,如props:[],如果要进行校验请使用对象形式,如props:{}),同时可以指定以下属性:
- type: 指定数据类型 String Number Object …注意不能使用字符串数组,只能是对象大写形式
- required: 指定是否必输
- default: 给默认值或者自定义函数返回默认值
- validator: 自定义函数校验
形式如下:
Vue.component('example', {
props: {
// 基础类型检测 (`null` 指允许任何类型)
propA: Number,
// 可能是多种类型
propB: [String, Number],
// 必传且是字符串
propC: {
type: String,
required: true
},
// 数值且有默认值
propD: {
type: Number,
default: 100
},
// 数组/对象的默认值应当由一个工厂函数返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10
}
}
}
})
举例
完整代码:
<div id="app">
<child :a="msg" :c="greetText" :f="hello"></child>
</div>
<script>
Vue.component('child', {
//声明props 检验
props:{
'a': String,
'b': [Number,String],
'c': {
required:true
},
'd':{
default:100
},
e:{
type: Number,
default: function () {
return 1;
}
},
f:{
type:Number,
validator: function (value) {
return value < 100;
}
}
},
template:'<span>{{a}} == {{d}} == {{e}} == {{f}}</span>'
});
var app = new Vue({
el:'#app',
data:{
msg:'来自父组件的消息',
greetText:'你好Child',
hello:12
}
});
</script>
结果展示
注意点4:props是用来接收传递过来的属性值,最后会统一绑定到vc上,最好不要直接修改props的属性值(也就是不要直接修改vc上面的props接收的属性值,会报错),会报错如图,所以为了避免这个问题,最好的解决方案是在data中重新定一个新属性值,用来接收props中传递过来的参数属性
注意点5:组件标签传递的属性名也是有限制的,不能啥都瞎传,比如你想传递key就会报错如图,报错说key已经被征用了,不让使用
<Student name="李四" sex="女" :age="18" key=”123’/>
props:['name','age','sex',key]
3.12非props属性
标签引用子组件时,非定义的props属性,自动合并到子组件上,class和style也会自动合并,如果class或者style重复采用就近原则。
举例:定义子组件设置class和style,而标签引用子组件时也设置了class和style,那么会进行属性合并,如果重名采用就近原则。
<div id="app">
<child data-index="0" class="cont" style="font-size: 20px;"></child>
</div>
<script>
/***
* 引用子组件: 非定义的prop属性,自动合并到子组件上,class和style也会自动合并
* */
Vue.component('child', {
template:'<span class="item" style="color:red;">我是child</span>'
});
var app = new Vue({
el:'#app'
});
</script>
结果展示
3.13自定义事件
适用于:子组件 =》 给父组件传值
父组件给子组件传值使用props属性, 那么需要子组件更新父组件时,要使用自定义事件$on和$emit:
- $on监听: 不能监听驼峰标示的自定义事件, 使用全部小写(abc)或者-(a-b-c)
- $emit主动触发: $emit(事件名,传入参数)
3.13.1自定义事件绑定到子组件
注意点1:
问题:子组件调用父组件方法时传参,父组件如何接收到参数值?
1)如果只传递一个参数,比如:this.$emit('update-count', "你是谁?");
那么子组件标签形参可不带参数或者形参使用$event
<child v-on:update-count="changeCount"></child>
或者<child v-on:update-count="changeCount($event)"></child>
那么父组件(vue实例)方法中通过value即可接收参数比如: changeCount:function(value)
2)如果传递多个参数,比如: this.$emit('update-count', "ldz",29);,
那么子组件标签形参请使用arguments
<child v-on:update-count="changeCount(arguments)"></child>
那么父组件(vue实例)方法中通过value[index]即可接收参数,比如:
changeCount:function(value){
console.log("@" + value[0]);
console.log("@" + value[1]);
有2种方式都可以实现父组件接收子组件传递的多个参数
方式1(推荐方式1,因为好记):调用子标签不写括号和形参,但方法形参使用…
子组件
this.$emit('student1Send', this.ipt, this.ipt2)
父组件
<B ref="childrenB" @student1Send="getB"></B>
methods: {
getB(...args) {
this.strb = args[0];
this.strb2 = args[1];
}
方式2:调用子标签写括号和形参arguments,那么方法形参不使用…
子组件
this.$emit('student1Send', this.ipt, this.ipt2)
父组件
<B ref="childrenB" @student1Send="getB(arguments)"></B>
methods: {
getB(args) {
this.strb = args[0];
this.strb2 = args[1];
}
注意点2:子组件标签中形参必须叫arguments,而父组件中方法形参function(value),这个value名字可以叫任意名字
举例1:模拟只传递一个参数
1)声明父组件
var app = new Vue({
el:'#app',
data:{
count:0
},
methods:{
//定义计数方法
changeCount:function(value){
console.log(value);
//计数
this.count += 1;
}
}
});
2)自定义事件
<div id="app">
<!-- 自定义事件update-count -->
<child v-on:update-count="changeCount"></child>
<p>{{count}}</p>
</div>
在事件v-on:update-count中的update-count就是自定义事件的名字,不要使用驼峰标示,html不区分大小写,会导致子元素无法主动触发父组件的自定义事件。
3)定义子组件
Vue.component('child', {
template:'<button v-on:click="update">子组件Child</button>',
methods:{
update: function () {
console.log('update');
//主动触发事件执行
this.$emit('update-count', '子组件参数');
}
}
});
子组件child中定义的update方法,内部通过$emit(‘update-count’)主动触发父元素事件的执行。
结果展示
举例2:模拟传递多个参数
子组件
<template>
<div>
<button @click="student1Send()">点击学生1组件发送消息</button>
</div>
</template>
<script>
export default {
name: "B",
data() {
return {
ipt:"我是学生1组件发过来数据",
ipt2:"我是你爸爸"
}
},
methods: {
student1Send() {
this.$emit('student1Send', this.ipt, this.ipt2)
},
}
}
</script>
<style scoped>
</style>
父组件
<template>
<div id="app">
<B ref="childrenB" @student1Send="getB(arguments)"></B>
<p>接收的b数据:{{strb}}</p>
<p>接收的b数据:{{strb2}}</p>
</div>
</template>
<script>
import B from "./B.vue"
export default {
name: "A",
data() {
return {
strb:"",
strb2:"",
}
},
components: {
B
},
methods: {
getB(args) {
console.log(args);
this.strb = args[0];
this.strb2 = args[1];
console.log(this.$refs.childrenB)
}
},
结果展示
3.13.2自定义事件挂载到父组件
自定义事件不仅可以绑定在子组件,也可以直接挂载到父组件,使用$on绑定和$emit触发。
2种方式区别:
自定义事件绑定到子组件,其实是调用定义子组件方法 -> 调用自定义事件 -> 调用父组件方法。
自定义事件挂载到父组件,其实是vue实例直接配置自定义函数从而达到操作父组件属性,第一步通过app.$on主动挂载自定义函数名,主动挂载就等于vue实例定义method方法,第二步通过app.$emit主动触发事件。
<div id="app">
</div>
var app = new Vue({
el:'#app',
data:{
count:0
}
});
//主动挂载
app.$on('update-count', function(value){
console.log(value);
//计数
this.count += 1;
});
app.$emit('update-count',123);
3.13.3自定义事件-解绑
自定义事件既然可以定义绑定,那么肯定也支持解绑,所谓解绑就是让自定义事件失效。
语法:this.$off(‘自定义事件名’)
this.$off('atguigu') //解绑一个自定义事件
this.$off(['atguigu','demo']) //解绑多个自定义事件
this.$off() //解绑所有的自定义事件
3.13.4使用自定义事件的容易出错的点
案例1:有3个组件,父组件App.vue,2个子组件Student.vue和School.vue,想实现点击子组件按钮把学生名传递给App,并在父组件App上显示出来
初始化页面
点击按钮成功收到消息图片
完整代码:
App.vue
<template>
<div class="app">
<h1>{{msg}},学生姓名是:{{studentName}}</h1>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"/>
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
<Student @atguigu="getStudentName"/>
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student" />
</div>
</template>
<script>
import Student from './components/Student'
import School from './components/School'
export default {
name:'App',
components:{School,Student},
data() {
return {
msg:'你好啊!',
studentName:''
}
},
methods: {
getSchoolName(name){
console.log('App收到了学校名:',name)
},
getStudentName(name,...params){
console.log('App收到了学生名:',name,params)
this.studentName = name
},
},
mounted() {
// this.$refs.student.$on('atguigu',this.getStudentName) //绑定自定义事件
// this.$refs.student.$on('atguigu',function (name,...params) {
// console.log('App收到了学生名:',name,params)
// console.log("this:", this); //使用自定义组件的配置项的普通函数中this指代子组件Student.vue的vc
// this.studentName = name
// }) //绑定自定义事件
this.$refs.student.$on('atguigu',(name,...params) => {
console.log('App收到了学生名:',name,params)
console.log("this:", this); //使用自定义组件的配置项的箭头函数中this指代父组件App.vue的vc
this.studentName = name
}) //绑定自定义事件
},
}
</script>
<style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>
School.vue
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="sendSchoolName">把学校名给App</button>
</div>
</template>
<script>
export default {
name:'School',
props:['getSchoolName'],
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
methods: {
sendSchoolName(){
this.getSchoolName(this.name)
}
},
}
</script>
<style scoped>
.school{
background-color: skyblue;
padding: 5px;
}
</style>
Student.vue
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<button @click="sendStudentlName">把学生名给App</button>
</div>
</template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男',
number:0
}
},
methods: {
sendStudentlName(){
//触发Student组件实例身上的atguigu事件
this.$emit('atguigu',this.name,666,888,900)
},
},
}
</script>
<style lang="less" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
方式1:使用v-bind标签,父组件提前把函数给子组件,子组件用props接收并调用,这样父组件函数中就能收到子组件传过来的值,只贴出重要代码
App.vue
<School :getSchoolName="getSchoolName"/>
methods: {
getSchoolName(name){
console.log('App收到了学校名:',name)
}
}
School.vue
<button @click="sendSchoolName">把学校名给App</button>
props:['getSchoolName'],
methods: {
sendSchoolName(){
this.getSchoolName(this.name)
}
}
方式2:使用自定义函数,父组件App中定义自定义函数传递给子组件,子组件通过&emit触发自定义事件调用,父组件中定义函数接收传递过来的值
App.vue
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
<Student @atguigu="getStudentName"/>
getStudentName(name,...params){
console.log('App收到了学生名:',name,params)
this.studentName = name
}
Student.vue
<button @click="sendSchoolName">把学校名给App</button>
methods: {
sendStudentlName(){
//触发Student组件实例身上的atguigu事件
this.$emit('atguigu',this.name,666,888,900)
},
}
方式3:使用ref标签,子组件通过&emit触发自定义事件调用,在父组件App中使用ref获取子组件标签同时搭配&on获取传递过来的值
App.vue
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
<Student @atguigu="getStudentName"/>
methods: {
getStudentName(name,...params){
console.log('App收到了学生名:',name,params)
this.studentName = name
}
}
//第1种写法
mounted() {
this.$refs.student.$on('atguigu',this.getStudentName) //绑定自定义事件
}
---------------------------------------------------------------------------------------
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
<Student @atguigu="getStudentName"/>
//第2种写法
mounted() {
this.$refs.student.$on('atguigu',(name,...params) => {
console.log('App收到了学生名:',name,params)
console.log("this:", this); //使用自定义组件的配置项的箭头函数中this指代父组件App.vue的vc
this.studentName = name
}) //绑定自定义事件
}
容易出错点1:
在父组件App中使用$on的箭头函数中把子组件传递过来的值赋值给父组件属性,会导致父组件属性接收不到信息
失败图片
成功图片
答案:在父组件App中使用$on的普通函数this指代子组件的vc,而$on的箭头函数this指代父组件的vc,所以$on的普通函数的this.studentName = name就会赋值失败,正确写法就是上面说的,要么method定义赋值,$on中直接调用,要么$on中使用箭头函数去赋值
mounted() {
this.$refs.student.$on('atguigu',function (name,...params) {
console.log('App收到了学生名:',name,params)
console.log("this:", this); //使用自定义组件的配置项的普通函数中this指代子组件Student.vue的vc
this.studentName = name
}) //绑定自定义事件
}
---------------------------------------------------------------------------------------
mounted() {
this.$refs.student.$on('atguigu',(name,...params) => {
console.log('App收到了学生名:',name,params)
console.log("this:", this); //使用自定义组件的配置项的箭头函数中this指代父组件App.vue的vc
this.studentName = name
}) //绑定自定义事件
}
容易出错点2:
问题:我在子组件Student 标签中使用了自定义事件,我想再绑定个原生事件,为啥原生事件失效了?
<Student ref="student" @click="show"/> //错误写法
methods: {
show(){
alert(123)
}
}
答案:因为原生事件也使用@注解,子组件标签默认会把@标识的原生事件理解为是自定义事件,所以就失效了,解决办法是在@click后面加.native标识符,这样子组件就知道这个click方法是原生方法。
<Student ref="student" @click.native="show"/> //正确写法
再引出个思考点:为啥说子组件中根元素标签只能有一个,比如最外层只能有1个div标签,为啥能不能有2个?
答案:给子组件标签绑定【自定义事件/原生事件】,实际是给里面的整个标签绑定函数,如果你有2个div,它哪知道给谁,如果2个都给岂不乱套了,当然这只是其中一个理由,实际子组件根元素标签只让有一个元素还有好多原因,只不过我还没学到。
3.14插槽分发
父子组件使用时,有时需要将父元素的模板跟子元素模板进行混合,这时就要用到slot插槽进行内容分发, 简单理解就是在子模板定义中先占个位置等待父组件调用时进行模板插入。
3.14.1slot插槽
注意:在子组件模板定义中使用<slot>标签定义插槽位置,标签中可以填写内容,当父组件调用子组件且不传入内容时显示此<slot>标签体中内容,当父组件在引用<child>子组件时,标签中的内容会放在子组件的<solt>插槽中。
举例
完整代码:
<div id="app">
<!-- 传入数据 -->
<child :msg="msgText">
<!-- 传入模板,混合子模板 -->
<h4>父组件模板</h4>
<h5>模板混入....</h5>
</child>
</div>
<template id="child-template">
<div>
<div>我是子组件</div>
<div>{{msg}}</div>
<!-- 定义slot插槽进行占位 -->
<slot>我是默认内容,父组件不传入时我显示</slot>
</div>
</template>
<script>
Vue.component('child', {
template:'#child-template',
props:['msg']
});
var app = new Vue({
el:'#app',
data:{
msgText:'父组件数据'
}
});
</script>
结果展示
3.14.2具名插槽
具名插槽slot, 就是给插槽起个名字。在子组件定义时可以定义多个<slot>插槽,同时通过name属性指定一个名字就可实现匹配,如:<slot name=‘header’>,父组件引用时使用< slot=‘header’>进行匹配插槽插入元素。
注意点1:子组件模板定义了两个插槽header和footer,分别使用name属性进行名称的指定,父组件引用子组件的标签中通过slot属性,来确定内容需要分发到哪个插槽里面。
大白话讲:slot标签通过配置name="header"名字,而父组件引用子组件的待传入标签通过配置slot="header"进行匹配插槽位置。
举例
完整代码:
<div id="app">
<!-- 传入数据 -->
<child :msg="msgText">
<!-- 传入模板,混合子模板 -->
<h4 slot="header">头部</h4>
<h4 slot="footer">底部</h4>
</child>
</div>
<template id="child-template">
<div>
<!-- 插槽header -->
<slot name="header"></slot>
<div>我是子组件</div>
<div>{{msg}}</div>
<!-- 插槽footer -->
<slot name="footer"></slot>
</div>
</template>
<script>
Vue.component('child', {
template:'#child-template',
props:['msg']
});
var app = new Vue({
el:'#app',
data:{
msgText:'父组件数据'
}
});
</script>
结果展示
3.14.3作用域插槽slot-scope
作用域插槽slot-scope,父组件通过插槽混入子组件的内容, 子组件也可以通过slot作用域向插槽slot内部传入数据,使用方式:<slot text=‘子组件数据’>,父组件通过<template slot-scope=“props”>进行引用。
大白话讲就是:子组件插槽slot定义属性,让父组件待插入的标签内容可以直接使用slot定义的属性内容。
说明点1:在slot标签中指定属性值,类似于props属性的使用,只不过是反过来的。即:组件标签中props属性用于父传子,而slot标签中指定属性值,是子传父。
说明点2:引用时用template标签指定,slot-scope属性指定接收数据的变量名,就可以使用花括号形式取值了。
完整代码:
<div id="app">
<!-- 传入数据 -->
<child>
<!-- slot-scope的值可以随便起变量名 -->
<template slot-scope="props">
<div>{{msgText}}</div>
<div>{{props.text}}</div>
</template>
</child>
</div>
<template id="child-template">
<div>
<!-- 插槽text值 -->
<slot text="子组件数据" ></slot>
</div>
</template>
<script>
Vue.component('child', {
template:'#child-template'
});
var app = new Vue({
el:'#app',
data:{
msgText:'父组件数据'
}
});
</script>
3.14.4slot-scope版本更新
在2.5+之后,可以不局限于<template>, 任何元素都可以,同时可以使用解构赋值的方式进行数据解析。
子组件:
<template id="child-template">
<div>
<!-- 插槽text值 -->
<slot name="head" text="header"></slot>
<slot name="foot" text="footer" value="18"></slot>
<slot name="cont" text="content" title="main"></slot>
</div>
</template>
父组件使用:
<div id="app">
<!-- 传入数据 -->
<child>
<!-- div标签使用slot-scope -->
<div slot="head" slot-scope="props">子组件数据: {{props.text}} <span>{{fa}}</span></div>
<div slot="foot" slot-scope="props">{{props.text}} == {{props.value}}</div>
<!-- 结构赋值 -->
<div slot="cont" slot-scope="{text, title}">{{text}} == {{title}}</div>
</child>
</div>
js部分:
Vue.component('child', {
template:'#child-template'
});
var app = new Vue({
el:'#app',
data:{
fa:'father 数据'
}
});
结果展示
3.15.动态组件
使用<component>标签的is属性,动态绑定多个组件到一个挂载点,通过改变is绑定值,切换组件。
3.15.1使用方式
举例:点击a标签下方切换不同组件
<div id="app">
/ <a href='#' @click.prevent="page='index'">首页</a>
/ <a href='#' @click.prevent="page='news'">新闻</a>
/ <a href='#' @click.prevent="page='login'">登陆</a>
<hr>
<component :is="page"></component>
</div>
<script>
/***
* 使用<component>标签的is属性,动态绑定多个组件到一个挂载点,
* 通过改变is绑定值,切换组件
* */
Vue.component('index', {
template:'<h5>首页</h5>'
});
Vue.component('news', {
template:'<h5>新闻页</h5>'
});
Vue.component('login', {
template:'<h5>登陆页</h5>'
});
var app = new Vue({
el:'#app',
data:{
page:'index'
}
});
</script>
结果展示
3.15.2keep-alive
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令。
注意点1:使用keep-alive标签嵌套component标签
注意点2:用生命周期中的mounted(挂载)钩子函数进行组件渲染监听,当组件第一次被渲染后就保存在内存中,下次切换不会被重新渲染。
注意点3:所谓的“避免重新渲染”指的是初始化后首次调用会渲染并打印输出钩子语句,等后续再次点击就不会触发打印,即达到了避免重新渲染的目的。
<div id="app">
/ <a href='#' @click.prevent="page='index'">首页</a>
/ <a href='#' @click.prevent="page='news'">新闻</a>
/ <a href='#' @click.prevent="page='login'">登陆</a>
<hr>
<keep-alive>
<component :is="page"></component>
</keep-alive>
</div>
Vue.component('index', {
template:'<h5>首页</h5>',
mounted: function () {
console.log('挂载...首页');
}
});
Vue.component('news', {
template:'<h5>新闻页</h5>',
mounted: function () {
console.log('挂载...新闻页');
}
});
Vue.component('login', {
template:'<h5>登陆页</h5>',
mounted: function () {
console.log('挂载...登陆页');
}
});
var app = new Vue({
el:'#app',
data:{
page:'index'
}
});
3.16 refs属性
使用ref 给每个组件起一个固定的名字,方便后续直接引用操作,在父组件中使用$refs访问子组件。
举例:使用自定义组件,默认打印count值为0,通过配置app.$refs.btn1.count = 1,父组件就可以达到获取ref标识的自定义组件并修改count值的目的。
<div id="app">
<child ref="btn1"></child>
</div>
<script>
/***
* ref 给每个组件起一个固定的名字,方便后续直接引用操作
* */
Vue.component('child', {
template:'<button>{{count}}</button>',
data:function(){
return {count:0}
}
});
var app = new Vue({
el:'#app'
});
app.$refs.btn1.count = 1;
</script>
3.17.生命周期
3.17.1生命周期介绍
注意点1: 生命周期钩子函数中this指代vue对象,注意和监视属性下面图1中的this做对比,别混。
(即:
计算属性使用同步操作的普通函数,this => vue,
同步操作的箭头函数,this => window
监视属性的同步操作的普通函数,this => vue,
同步操作的箭头函数,this => window,
异步操作的普通函数,this => window,
异步操作的箭头函数,this => vue,具体原因看下面图)。
3.17.2生命周期钩子
每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到 DOM、在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,给予用户机会在一些特定的场景下添加他们自己的代码。
比如 created 钩子可以用来在一个实例被创建之后执行代码;
常用的生命周期钩子函数有:
- 1 created: 实例创建完成后被立即调用
- 2)mounted: 实例挂载完成后被立即调用
- 3 beforeUpdate: 实例需要渲染之前调用
- 4)updated: 实例更新后调用
- 5)destroyed: Vue 实例销毁后调用
3.17.3生命周期整体流程图
注意点1:
问题:钩子函数beforeCreate和created代表创建前和创建后,这个创建指的是谁?
答案:指的是“数据监测和数据代理”也就是data属性和属性的get()和set(),一定不是指代vue实例,千万别弄混。
注意点2:图中的判断元素“Has template option?”中的template 指的是图1元素(也就是data中的template属性),而不是图2元素(图2中的template 是标签)
注意点3:图3中的outerHtml指的是图4中绿色框内容,与其对立的interHtml指的才是红色框内容
注意点4:
问题:图5步骤会把之前生成内存中的虚拟DOM转成真实DOM,转成之后会往“vm.$el”上存了一份,问:为什么要保存一份?
答案:可以用在很多地方,比如vue提供了虚拟DOM和真实DOM比较算法,既然有比较那么去哪里拿虚拟DOM呢?诶,“vm.$el”上面就保存了可以直接使用。
注意点5:
问题:图6中红框如何理解?,为啥最终都不奏效,注意这个“最终”字眼
原因:钩子函数beforeMount中无论做了什么操作都没用,因为执行到图7红框操作部分,只会把内存中虚拟DOM转为真实DOM插入页面中,会进行模板渲染覆盖,这样就会导致钩子函数beforeMount中无论做了什么操作都没用了。
注意点6:在div中定义模板和在data中定义模板,页面长得不一样。
举例说明:div中定义模板如图8,页面效果如图9,页面会发现root容器还在,而如果data中定义模板如图10,页面效果如图11,页面会发现root容器消失了。总结大白话就是说,如图12使用data中定义模板方式,会造成绿色框会替换掉红色框内容,就会造成原来红色框定义的x属性等等都消失了,都白写了。
注意点7:面试官可能会问,在哪个钩子中页面和数据尚未保持同步啊?所谓数据未同步指的是data数据值已经改了,而页面却没进行动态更新。如图13
答案就是beforeUpdate钩子函数中
注意点8:官网红框容易造成歧义
注意点9:一般不会在钩子函数beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。大白话说就是哪怕钩子函数beforeDestroy中执行修改属性操作了,值也改变了,但是页面也不会触发更新操作了,即值改了 但页面不更新了。
注意点10:vue生命周期流程图中只展示了8个钩子,实际还有3个隐藏的钩子没显现出来,它们旨在特殊场合才会显示出来,即“实现路由切换”功能时才会讲诉这3个隐藏的钩子。
注意点11:
3个隐藏的钩子:nextTick、activated、deactivated
其中:nextTick请看3.24知识点,而activated、deactivated用于路由组件。
举例:代码说明
<div id="root">
{{name}}
</div>
<script>
var vm = new Vue({
el:'#root',
data:{
name:'Tim'
},
created: function () {
console.log('实例创建...');
},
mounted:function () {
console.log('实例挂载...');
},
beforeUpdate:function () {
console.log('实例将要更新...');
},
updated:function () {
console.log('实例已更新...');
},
destroyed:function(){
console.log('实例卸载...');
}
});
//改变name
vm.name = 'Cat';
//vm.$destroy();//卸载
</script>
结果展示
3.18混入
注意点1:所谓“混入”,就是把vue组件中共同的配置提取出来,单独用一个文件保存,比如叫mixin.js保存配置,使用时引入并配置即可使用。
注意点2:
问题:如何使用混入?
答案:第一步引入,第二步配置mixins。
mixin.js
export const hunhe = {
methods: {
showName(){
alert(this.name)
}
},
mounted() {
console.log('你好啊!')
},
}
export const hunhe2 = {
data() {
return {
x:100,
y:200
}
},
}
组件内使用时引入并配置
import {hunhe,hunhe2} from '../mixin'
export default {
name:'School',
data() {
return {
name:'尚硅谷',
address:'北京',
x:666
}
},
mixins:[hunhe,hunhe2],
}
注意点3:mixin.js中写法export const hunhe = {},这叫ES模块化的分别暴露。
注意点4:组件中使用混入,需指定mixins:[],必须为数组才有效。
注意点5:所有的vue的配置项都可以写在“混入”里面。
注意点6:混入文件mixin.js中有一个属性名,而data中未定义相同的属性名,那么值以“混入”中定义的为主,如果mixin.js中和data中有相同属性名,那么以data中配置的数据或方法为主,说白了“混入”不破坏你的代码。
注意点7(特殊情况):
问题:和注意点6对比,比如“混入”中定义了钩子函数,而data中定义了相同名称的钩子函数,那么以谁为主?
答案:钩子函数会特殊处理,vue不以任何人为主,vue它都要,另外注意加载顺序,“混入”中钩子函数先加载,组件中同名钩子函数后加载。
注意点8:混入有局部混入,还有全局混入
“局部混入”方式,使用mixins:[]
组件中
<script>
//引入一个hunhe
import {hunhe,hunhe2} from '../mixin'
export default {
name:'School',
data() {
return {
...
}
},
mixins:[hunhe,hunhe2],
}
“全局混入”方式,使用Vue.mixin(),注意弊端:Vue.mixin(hunhe)会导致所有的vm、vc都拥有“混入”配置
main.js中
import {hunhe,hunhe2} from './mixin'
Vue.mixin(hunhe)
Vue.mixin(hunhe2)
注意点9:使用“混入”步骤还是3步:
- 1)定义,提取成mixin.js文件,放在跟A匹配。App.vue同级别下
- 2)注册,分局部)注册和全局)注册
- 3)main.js中引入
3.19插件
注意点1:
问题:vue插件是啥?
答案:vue插件是在实例化vm之前,使用插件能够动态新增一系列功能的东西,比如添加一堆全局过滤器、或者添加一堆全局指令…。(大白话总结:插件就是类似外挂,每次进游戏前会动态新增一系列暴力功能)
注意点2:
问题:如何使用插件?
答案:src目录下新建pluins.js文件,里面写类似下面这样的代码,在main.js中使用Vue.use(plugins)命令去添加插件
pluins.js文件
export default {
install(Vue,x,y,z){
console.log(x,y,z)
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})
//定义全局指令
Vue.directive('fbind',{
//指令与元素成功绑定时(一上来)
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
//指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
}
})
//定义混入
Vue.mixin({
data() {
return {
x:100,
y:200
}
},
})
//给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.hello = ()=>{alert('你好啊')}
}
}
main.js文件
Vue.use(plugins)
注意点3:定义插件配置中函数,必须叫install(),不能改名
注意点4:install()接收的第一个参数叫Vue,这个Vue指的是缔造vm实例的Vue原型的构造函数
注意点5:使用插件时是可以传递多个参数的,比如
main.js文件
Vue.use(plugins,1,2,3)
pluins.js文件
export default {
install(Vue,x,y,z){
console.log(x,y,z)
}
3.20 标签属性scoped用于控制样式
注意点1:
问题:我有两个子组件,拥有重复名称但样式不同的class,那么父组件在加载两个子组件时以谁的样式为准呢?
答案:如图1以import的加载顺序为准,后加载会覆盖同名先加载的样式,而不以使用组件标签顺序为准,那么如何解决该问题呢?答案是在组件样式标签中配置scoped,比如如图2,它是怎么办到的呢,其实啊配置scoped后相当于动态给<template>标签内的<div>标签中加了个特殊属性,而且后面的值是每次运行随机生成的,通过”该特殊属性值“配合”控制标签属性选择器“一同控制样式,具体如图3
注意点2:<style scoped>配置后只对当前组件页的<template>标签内的内容有效
注意点3:有一个组件配置scoped不太适合,它就是App.vue组件,因为它是头部,如果配置scoped,那么只有当前组件页App.vue中的<template>标签内的内容使用该样式后才会生效,而其他组件哪怕配置了该样式也不会生效,除非App.vue中样式标签不配置scoped,这样子组件使用App.vue中样式才会生效
注意点4:组件中<style>标签中还可以指定css的编写方式,即配置<style lang=“less” scoped>或者<style lang=“css” scoped>
默认不配置lang就代表使用css编写方式:<style scoped> = <style lang=“css” scoped>
3.21浏览器本地缓存
浏览器本地缓存实际是js的知识点,这里只是提一下加深下印象
分两种:localStorage和sessionStorage,它两统称webStorage
注意点1:localStorage对象和sessionStorage对象都是window对象下的,且方法都是一样的,默认”window.”可以省略,添加可用setItem(K,V),查询可用getItem(K),删除可用removeItem(K),清空可用.clear()
注意点2:本地缓存默认存储都是字符串类型,哪怕存值为数值,最终结果也会转成字符串类型值
注意点3:查询本地缓存getItem(K),如果查询一个不存在的key值,结果为null,而不是undefine,另外JSON.parse(null),那么结果依然是null,而不是undefine
注意点4:如果存储值为对象类型,那么页面缓存保存的实际是调用.toString()后的字符串效果,如图,这样坏处是压根不知道对象里面都有啥属性,哪怕获取到该对象也不明白都包含了啥,使用很不方便,而不是咱们认识的json格式
let p = {name:'张三',age:18}
function saveData(){
localStorage.setItem('person',p)
}
控制它i打印展示
注意点5:如果想把对象值[Object Object]转为咱们认识的json格式,可使用JSON.stringify(json对象)和JSON.parse([Object Object])
let p = {name:'张三',age:18}
localStorage.setItem('person',JSON.stringify(p))
const result = localStorage.getItem('person')
console.log(JSON.parse(result))
注意点6:localStorage对象关闭网站也不会清除,而sessionStorage对象关闭网站就都会清楚了
注意点7:localStorage存储的内容如果清除有2种方式
方式1:调用提供的API清除
方式2:调用了clear()方法清空整个缓存去清除
3.21.1localStorage
举例:写一个简单的针对本地存储增删改查的案例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>localStorage</title>
</head>
<body>
<h2>localStorage</h2>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="deleteAllData()">点我清空一个数据</button>
<script type="text/javascript" >
let p = {name:'张三',age:18}
function saveData(){
localStorage.setItem('msg','hello!!!')
localStorage.setItem('msg2',666)
localStorage.setItem('person',JSON.stringify(p))
}
function readData(){
console.log(localStorage.getItem('msg'))
console.log(localStorage.getItem('msg2'))
const result = localStorage.getItem('person')
console.log(JSON.parse(result))
// console.log(localStorage.getItem('msg3'))
}
function deleteData(){
localStorage.removeItem('msg2')
}
function deleteAllData(){
localStorage.clear()
}
</script>
</body>
</html>
页面新增缓存效果
页面查询缓存效果
页面删除缓存效果
页面清空缓存效果
3.21.2sessionStorage方法同localStorage一样
3.22全局事件总线(GlobalEventBus)
使用步骤:
1.main.js定义“全局事件总线”
2.在A组件想接收数据,定义this.$bus.$on和this.$bus.$off
3.在B组件想发送数据,定义this.$bus.$emit
1.main.js定义“全局事件总线”
//创建vm
new Vue({
el:'#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this
}
})
2.在A组件想接收数据,定义this.$bus.$on和this.$bus.$off
mounted() {
// console.log('School',this)
this.$bus.$on('hello',(data)=>{
console.log('我是School组件,收到了数据',data)
})
},
beforeDestroy() {
this.$bus.$off('hello')
}
3.在B组件想发送数据,定义this.$bus.$emit
methods: {
sendStudentName(){
this.$bus.$emit('hello',this.name)
}
}
注意点1:
问题:“全局事件总线”需要哪些特点?
答案:
1)被所有组件(vc、vm)能够看得见
2)能够调用$on、$emit、$off
注意点2:
问题:Vue原型对象上面的所有属性和方法是给谁用的?
答案:是给所有的vm和vc使用的
注意点3:
问题:为什么定义“全局事件总线”要放在main.js文件中?
答案:因为哪里引入Vue,哪里才会去定义“全局事件总线”
注意点4:
问题:为什么定义“全局事件总线”要放在beforeCreate的钩子函数中?
答案:原因1,beforeCreate钩子函数里this指代new出来的vm,原因2,在beforeCreate钩子函数里模板还没解析,数据监测和数据代理也还没完成呢。也就是说借助这个beforeCreate钩子函数你把想做的事儿做好了,原型上该放的放好了,随后模板开始解析,等组件执行的时候你该放的都放好了,后续才做都不会产生影响。
注意点5:
问题:如何避免在使用“全局事件总线”时自定义函数名重名使用问题?比如组件1使用自定义函数名叫demo,那组件2不全文搜索也使用了自定义函数名也叫demo,这就混了
答案:真实项目中src目录下创建一个config文件夹,里面创建个constants常量文件,里面用来定义要使用的自定义函数名,方便别人查看并避免重名问题。
注意点6:
问题:为什么要在组件销毁之前,把“全局事件总线”中定义的自定义事件函数解绑?那“知识点3.13自定义事件”中咋没说解绑的事儿呢?
答案:“知识点3.13自定义事件”中组件销毁了== vc销毁了,vc销毁了自定义事件也就销毁了,而“全局事件总线”中定义的自定义函数是一直存在的,哪怕使用组件销毁了,但是Vue实力定义的“全局事件总线”中还是会存在自定义事件,所以需要在组件销毁之前进行解绑。
注意点7:销毁“全局事件总线”中定义的自定义事件请放在beforeDestroy()钩子中
注意点8:子组件中使用“全局事件总线”时this.$bus.$on()中回调配置要使用箭头函数,不要使用普通函数,箭头函数中this才指代vc,而普通函数中this指代vue实例,因为最终要在school组件上接收平行组件发过来的消息,所以要使用vc,而不是要使用vue实例,因为vue实例不是我们最终要的。
案例:平行组件通信,把学生组件名称发给学校组件中,并打印出来
项目结构
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
//创建vm
new Vue({
el:'#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线
},
})
App.vue
<template>
<div class="app">
<h1>{{msg}}</h1>
<School/>
<Student/>
</div>
</template>
<script>
import Student from './components/Student'
import School from './components/School'
export default {
name:'App',
components:{School,Student},
data() {
return {
msg:'你好啊!',
}
}
}
</script>
<style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>
Student.vue
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男',
}
},
mounted() {
// console.log('Student',this.x)
},
methods: {
sendStudentName(){
this.$bus.$emit('hello',this.name)
}
},
}
</script>
<style lang="less" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
School.vue
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>
<script>
export default {
name:'School',
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
mounted() {
// console.log('School',this)
this.$bus.$on('hello',(data)=>{
console.log('我是School组件,收到了数据',data)
})
},
beforeDestroy() {
this.$bus.$off('hello')
},
}
</script>
<style scoped>
.school{
background-color: skyblue;
padding: 5px;
}
</style>
结果展示:
3.23消息订阅与发布
3.23.1知识点总结
问题:“全局事件总线”和“消息订阅与发布”都可以实现任意组件间通信,那用哪个好?
答案:推荐使用“全局事件总线”,因为它是vue提供的,完全使用的vue技术,而“消息订阅与发布”则是第三方。
3.23.2案例
案例:跟全局事件总线一样,利用“消息订阅与发布”实现平行组件间通信,即学生组件发送数据给学校组件
注意点1:由于“消息订阅与发布”可依赖的第三方太多了,这里使用pubsub-js
注意点2:使用语法
消息订阅语法
import pubsub from 'pubsub-js'
mounted() {
this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
// console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
})
},
beforeDestroy() {
// this.$bus.$off('hello')
pubsub.unsubscribe(this.pubId)
}
消息发布语法
import pubsub from 'pubsub-js'
pubsub.publish('hello',666)
注意点3:取消订阅方式和“全局事件总线”不同,取消订阅指定订阅返回的id,且每次返回的id都不同,而“全局事件总线”指定的是“自定义事件名称”
注意点4:订阅回调配置一定要使用箭头函数或者外部定义方法,在订阅中引用也行,千万不要使用普通函数,因为普通函数中this不指代vc,而是undefine,这一点跟“全局事件总线”中的注意点8很像,但还是略有不同
注意点5:消息订阅会接收到2个参数,第1个参数为消息名称,第2个参数才是传递过来的值,如写法1,但是实际msgName参数1他跟用不到它,所以可使用下划线“_”占个位,如写法2
写法1:
this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
// console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
})
写法2:
this.pubId = pubsub.subscribe('hello',(_,data)=>{
// console.log('有人发布了hello消息,hello消息的回调执行了',_,data)
})
项目结构
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
//创建vm
new Vue({
el:'#app',
render: h => h(App),
})
App.vue
<template>
<div class="app">
<h1>{{msg}}</h1>
<School/>
<Student/>
</div>
</template>
<script>
import Student from './components/Student'
import School from './components/School'
export default {
name:'App',
components:{School,Student},
data() {
return {
msg:'你好啊!',
}
}
}
</script>
<style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>
Student.vue
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男',
}
},
mounted() {
// console.log('Student',this.x)
},
methods: {
sendStudentName(){
// this.$bus.$emit('hello',this.name)
pubsub.publish('hello',666)
}
},
}
</script>
<style lang="less" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
School.vue
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name:'School',
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
mounted() {
// console.log('School',this)
/* this.$bus.$on('hello',(data)=>{
console.log('我是School组件,收到了数据',data)
}) */
this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
console.log(this)
console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
})
},
beforeDestroy() {
// this.$bus.$off('hello')
pubsub.unsubscribe(this.pubId)
},
}
</script>
<style scoped>
.school{
background-color: skyblue;
padding: 5px;
}
</style>
结果展示:
3.24 nextTick语法
说明:类似于定时器的功能
问题:什么场景会使用它呢?
答案:举例我想有个input输入框,我还有个“编辑”按钮,如图1,如图2,点击“编辑”按钮input输入框就聚焦,失焦就修改成功输入框的值。如图3你想实现45行到50行代码执行完,页面模板就重新解析渲染更新数据,然后你执行52行代码输入框就聚焦了,但实际页面显示压根不聚焦为啥?因为vue会把整个方法都执行完才会去渲染更新DOM节点(为啥要这么设计?因为如果一个方法里面修改8个属性值,我每修改一个DOM就渲染一次也太消耗资源了,所以设计成方法执行完毕了,用户属性都修改好了,我再去渲染DOM节点),所以52行会先执行,但是DOM还没渲染且输入框还没出来了,上哪里聚焦去,所以就出现了点击“编辑”按钮输入框聚焦失败的场景。
3.25Vue封装的过度与动画
3.25.1知识点总结
3.25.2案例
注意点1:最好有css动画基础,再练习这块,但我只是了解所以原封不动拷贝看效果就是,当了解即可。
【动画/过度】使用方式:
1)定义【动画/过度】样式名称
2)用标签包裹起来要实现动画的元素
使用动画方式:
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
<style scoped>
h1{
background-color: orange;
}
.hello-enter-active{
animation: atguigu 0.5s linear;
}
.hello-leave-active{
animation: atguigu 0.5s linear reverse;
}
@keyframes atguigu {
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>
使用过度方式:
<style scoped>
h1{
background-color: orange;
}
/* 进入的起点、离开的终点 */
.hello-enter,.hello-leave-to{
transform: translateX(-100%);
}
.hello-enter-active,.hello-leave-active{
transition: 0.5s linear;
}
/* 进入的终点、离开的起点 */
.hello-enter-to,.hello-leave{
transform: translateX(0);
}
</style>
注意点2:动画效果来和去只写一个就行,另一个效果直接反转动画就是
注意点3:vue要求你想让谁实现动画效果,你就用标签把它包裹起来
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
注意点4:这个和标签效果一样,最终页面都不会显示这个标签,它只作为包裹作用使用
注意点5:每个过度可以取名字,如果<transition>定义了name属性值,那么class的样式名称前缀也得改名,不然无法自动识别,比如未定义name属性,那么类名叫.v-enter-active和.v-leave-active,如果定义了name属性name=“hello”,那么类名叫.hello-enter-active和.hello-leave-active,(即vue不跟动画进行对话,而是跟样式的名称进行对话。)
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
<style scoped>
h1{
background-color: orange;
}
.hello-enter-active{
animation: atguigu 0.5s linear;
}
.hello-leave-active{
animation: atguigu 0.5s linear reverse;
}
@keyframes atguigu {
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>
注意点6:
问题:想实现多个元素产生相同过度效果时,错误代码如下,运行发生了报错如图
<transition>
<h1 v-show="!isShow" key="1">你好啊!</h1>
<h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>
答案:报错说明<transition>标签只能用于一个元素,如果想实现多个元素相同效果,请使用<transition-group>标签。
问题:如果改成使用<transition-group>标签后,运行还是报错了,感觉更严重了,下面两个过度一个都没显示,且还报错了。
答案:正确写法就是必须指定key值,这块在讲解v-for的时候着重强调要定义key的属性。
注意点7:Test3.vue使用第三方库animate.css,所以需要额外安装animate.css
使用步骤:
1)安装 npm install --save animate.css
2)引入 import ‘animate.css’
3)使用3个标签即可实现【动画/过度】效果,发别是name、enter-active-class、leave-active-class,使用第三方库比较方便,就不用像Test1.vue和Test2.vue中定义一堆动画或过度<style>标签内容
项目结构
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
//创建vm
new Vue({
el:'#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this
},
})
App.vue
<template>
<div>
<Test/>
<Test2/>
<Test3/>
</div>
</template>
<script>
import Test from './components/Test'
import Test2 from './components/Test2'
import Test3 from './components/Test3'
export default {
name:'App',
components:{Test,Test2,Test3},
}
</script>
Test.vue
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
</div>
</template>
<script>
export default {
name:'Test',
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
h1{
background-color: orange;
}
.hello-enter-active{
animation: atguigu 0.5s linear;
}
.hello-leave-active{
animation: atguigu 0.5s linear reverse;
}
@keyframes atguigu {
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>
Test2.vue
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group name="hello" appear>
<h1 v-show="!isShow" key="1">你好啊!</h1>
<h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>
</div>
</template>
<script>
export default {
name:'Test',
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
h1{
background-color: orange;
}
/* 进入的起点、离开的终点 */
.hello-enter,.hello-leave-to{
transform: translateX(-100%);
}
.hello-enter-active,.hello-leave-active{
transition: 0.5s linear;
}
/* 进入的终点、离开的起点 */
.hello-enter-to,.hello-leave{
transform: translateX(0);
}
</style>
Test3.vue
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
</div>
</template>
<script>
export default {
name:'Test',
data() {
return {
isShow:true
}
},
}
</script>
<style scoped>
h1{
background-color: orange;
}
.hello-enter-active{
animation: atguigu 0.5s linear;
}
.hello-leave-active{
animation: atguigu 0.5s linear reverse;
}
@keyframes atguigu {
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>
3.26.vm调用待$命令介绍
1)vm.$set(target, property/index, value)
向响应式对象中添加一个property属性
2)vm.$watch(property, {config})
配置监听属性
3)vm.$emit(事件名, 传入参数)
主动触发事件,用于子组件更新父组件属性
4)vm.$on(event, callback)
主动挂载绑定自定义函数名 一般vm.$on和vm.$emit搭配使用,并且必须指定同一个vue实例,否则无效
5)vm.$mount(“app”)
绑定挂载vue的实例id
6)vm.$destroy()
销毁vm实例
本人其他相关文章链接
1.《基础篇第1章:vue2简介》包含Vue2知识点、个人总结的使用注意点及碰到的问题总结
2.《基础篇第2章:vue2基础》包含Vue2知识点、个人总结的使用注意点及碰到的问题总结
3.《进阶篇第3章:vue进阶-组件》包含组件、自定义事件、插槽、路由等等扩展知识点
7.vue2知识点:列表渲染(包含:v-for、key、取值范围、列表过滤、列表排序、vue监视对象或数组的数据改变原理、总结vue数据监测)
9.vue2知识点:生命周期(包含:生命周期介绍、生命周期钩子、整体流程图详解)
13.vue2知识点:组件的props属性、非props属性、props属性校验
16.vue2知识点:动态组件
17.vue2知识点:混入
19.vue2知识点:全局事件总线(GlobalEventBus)
23.vue2知识点:路由
25.vue组件通信案例练习(包含:父子组件通信及平行组件通信)
26.vue表单案例练习:vue表单创建一行数据及删除数据的实现与理解
27.vue2基础组件通信案例练习:待办事项Todo-list案例练习
28.vue2基础组件通信案例练习:把案例Todo-list改写成本地缓存
29.vue2基础组件通信案例练习:把案例Todo-list改成使用自定义事件
30.vue2基础组件通信案例练习:把案例Todo-list改成使用全局事件总线
31.vue2基础组件通信案例练习:把案例Todo-list改成使用消息订阅与发布
32.vue2基础组件通信案例练习:把案例Todo-list新增编辑按钮