vue -【生命周期】-【组件】-【脚手架】-【ref】-【props】-【混入mixin】-【插件】-【scoped】-【webStorage】-【自定义事件】-【全局事件总线】-【消息订阅与发布】

生命周期/生命周期回调函数/生命周期函数/生命周期钩子

一、介绍:

  1. 是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数。
  2. 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
  3. 生命周期函数中的this指向是vm或组件实例对象。

在这里插入图片描述

二、new Vue之后做了什么
5. 初始化生命周期、事件(其实就是制定一些规则,如生命周期有哪些?叫什么?什么时候去调用?事件修饰符都是干啥的?)。但数据代理未开始,即new Vue时传入的data还没有出现在vm身上,vm连_data都还没有
6. 调用beforeCreate(),此时无法通过vm访问到data中的数据、methods中的方法。很正常,此时数据代理还没开始,vm连_data都还没有
7. 初始化数据监测(vue如何检测对象变化、数组变化,给对象中的属性匹配getter和setter,对操作数组的方法进行二次包装)、数据代理。此时有vm上有_data了
8. 调用created(),此时可以通过vm访问到data中的数据、methods中的方法。

注意:beforeCreate()和created()指的是数据监测和数据代理创建前、创建完成

  1. 判断new Vue时是否传入el配置项?(此阶段vue开始解析模板(解析插值语法,@click、计算属性等),生成虚拟DOM保存在内存中,页面还不能显示解析好的内容(这个东西还没有变成真实DOM,还没有往页面上放),此时展示的是body标签中原始的未被vue解析内容,但是展示过程非常非常短,vue很快就接管root容器,编译模板,并呈现在页面上,所以未被vue解析的内容我们肉眼看不到
    (1)传了el配置项,判断new Vue时是否传入template配置项?
    (a)传了template配置项,直接去判断new Vue时是否传入template配置项。
    (b)没传template配置项,编译el的外部HTML就作为template,意思是root容器作为template,如果他说内部HTML作为template,就是指root容器中的内容作为template,二者的区别:<div id="root" :x="n"></div>
    如果将root容器作为template,会解析root容器的:x得到<div id="root" x="1"></div>
    如果将root容器中的内容作为template,不会解析root容器的:x,上述代码不变
    (2)没传el配置项,那么当vm.$mount(el)调用后,才去判断new Vue时是否传入template配置项。如果没传el配置项,也没调用vm.$mount(el),那么后面的步骤都不进行了

判断new Vue时是否传入template配置项?
(a)传了template配置项,将template的内容编译为render函数
(b)没传template配置项,编译el的外部HTML就作为template,意思是root容器作为template,如果他说内部HTML作为template,就是指root容器中的内容作为template,二者的区别:<div id="root" :x="n"></div>
如果将root容器作为template,会解析root容器的:x得到<div id="root" x="1"></div>
如果将root容器中的内容作为template,不会解析root容器的:x,上述代码不变

  1. 调用beforeMount(),此时页面呈现的是未经vue编译的DOM结构,所有对DOM的操作都不奏效,因为在下一步,他始终是将上一步生成的虚拟DOM转成真实DOM,你在这里不管怎么改,他转换的都是上一步的虚拟DOM
  2. 创建vm.$el并替换el(el指root容器中的东西),在这一步,vue把刚才生成的虚拟DOM转成真实DOM,并用vm.$el保存了一份真实DOM

为什么要创建vm.$el并保存一份真实DOM:vue在进行新旧虚拟DOM比对时,万一有的元素/节点可以复用,那他必须有之前的元素/节点,他才能去复用,

  1. 调用mounted(),此时页面中呈现的是经过vue编译的DOM,对DOM的操作均有效(但尽可能避免)。至此初始化过程结束。

一般在此阶段进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作
如果你在mounted()中操作DOM,就有一种感觉:你让vue先工作,生成虚拟DOM再转换成真实DOM,再把真实DOM挂在页面上了,人家工作完了,你反手一改,人家白忙活了,所以要尽可能避免对DOM的操作

以上就是挂载流程,不涉及新旧虚拟DOM比对,因为挂载时压根没有旧的虚拟DOM

  1. 当data中的数据改变时,vue调用beforeUpdate(),此时数据是新的,但页面时旧的,即:页面尚未和数据保持同步
  2. 虚拟DOM重新渲染,新旧虚拟DOM进行比对,也就是说,在这个阶段会根据新数据,生成新的虚拟DOM,随后与旧的虚拟DOM进行比较,最终完成页面的更新,即完成了model->view的更新
  3. 调用updated(),此时数据和页面都是新的,即数据和页面保持同步

以上就是更新流程

  1. vm.$destroy()被调用时,调用beforeDestroy(),此时vm中所有的data、methods、指令等等,都处于可用状态,马上要执行销毁过程,但是你在beforeDestroy()中对数据做的所有操作都不会触发更新。

一般在此阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作

  1. 移除所有的watchers(监视)、子组件、事件监听器
  2. 调用destroyed()

vm.$destroy()被调用时,vm就会自己销毁自己,会解绑全部指令和【自定义】事件监听器,但是vm之前的工作成果还在,只是之后没人帮你管理了

以上就是销毁流程

三、关于销毁Vue实例

  1. 销毁后借助Vue开发者工具看不到任何信息。
  2. 销毁后自定义事件会失效,但原生DOM事件依然有效。
  3. 一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。

new Vue的template配置项

template的值是个字符串,把root容器中的内容全写在这个字符串里(注意:template只能有一个根节点,所以root容器中的内容要被包在一个div标签中,注意不能包在template标签中,因为template标签不能作为组件根元素,因为template标签中可能包含多个节点),配置了下面这个template,就不用在root容器中写代码了。但是这种写法,它会用template中的内容完全替换掉root容器,也就是说body标签中的内容就变成了template中的内容,没有<div id="root" :x="n"></div>了,那么这样,你原本配置的:x="n"也就没有了。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>分析生命周期</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root" :x="n"></div>
	</body>
	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

		new Vue({
			el:'#root',
			template:`
			 	<div>
			 		<h2>当前的n值是:{{n}}</h2>
			 		<button @click="add">点我n+1</button>
			 	</div>
			`,
			data:{
				n:1
			},
			methods: {
				add(){
					console.log('add')
					this.n++
				}
			}
		})
	</script>
</html>

组件

一、组件:实现应用中局部功能代码和资源的集合
二、组件分为:非单文件组件、单文件组件

  1. 非单文件组件:一个文件中包含n个组件,缺点:样式不能跟着组件走
  2. 单文件组件:一个文件中只包含1个组件

三、Vue中使用组件的三大步骤:

  1. 定义组件(创建组件)
    使用Vue.extend(options)创建,其中optionsnew Vue(options)时传入的那个options几乎一样,但也有点区别,区别如下:
    (1)el不要写,因为最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
    (2)data必须写成函数,为了避免组件被复用时,数据存在引用关系:如果data是对象形式,那么想象一下这样一个场景:你定义了一个组件,组件中data是对象,a页面用了该组件,b页面也用了该组件,a页面改了data中的数据,那么b页面的数据也被改了,因为data是个对象,data中存放的是引用地址。把data定义为函数,就可以防止一个组件被多次使用时,存在数据的引用关系。每次获取数据时,都现场调用data函数,拿data函数的返回值给你用

备注:使用template可以配置组件结构。

  1. 注册组件
    (1)局部注册:靠new Vue的时候传入components选项
    (2)全局注册:靠Vue.component('组件名',组件)
  2. 使用组件(写组件标签):<school></school>

非单文件组件

基本使用

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>基本使用</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<div id="root">
			<!-- 第三步:编写组件标签 -->
			<hello></hello>
			<hr>
			<h1>{{msg}}</h1>
			<hr>
			<!-- 第三步:编写组件标签 -->
			<school></school>
		</div>

		<div id="root2">
			<!-- 所有的vm都可以使用全局注册的组件 -->
			<!-- 第三步:编写组件标签 -->
			<hello></hello>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false

		//第一步:创建school组件:Vue.extend({配置对象})
		const s = Vue.extend({
			name:'atguigu',//在开发者工具中的名字
			// 这个template和我们之前说的new Vue中的template配置项的用法一致
			template:`
				<div class="demo">
					<h2>学校名称:{{schoolName}}</h2>
					<h2>学校地址:{{address}}</h2>
					<button @click="showName">点我提示学校名</button>	
				</div>
			`,
			// el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
			data(){// data一定要写成函数形式,并且是普通函数
				return {
					schoolName:'尚硅谷',
					address:'北京昌平'
				}
			},
			methods: {
				showName(){
					alert(this.schoolName)
				}
			},
		})
		
		//第一步:创建hello组件
		const hello = Vue.extend({
			template:`
				<div>	
					<h2>你好啊!{{name}}</h2>
				</div>
			`,
			data(){
				return {
					name:'Tom'
				}
			}
		})
		
		//第二步:全局注册组件:Vue.component('组件真正叫的名字',组件所在的变量名)
		Vue.component('hello',hello)// 所有的vm都能用这个组件

		//创建vm
		new Vue({
			el:'#root',
			data:{
				msg:'你好啊!'
			},
			//第二步:注册组件(局部注册)
			components:{
				// 这里面写键值对,键是组件真正叫的名字,值是你上面第一步定义的变量名(组件所在变量)
				school: s,// 也就是说键名和你使用组件的标签名保持一致
			}
		})

		new Vue({
			el:'#root2',
		})
	</script>
</html>

几个注意点:

  1. 关于组件名:
    (1)一个单词组成:
    第一种写法(首字母小写):school
    第二种写法(首字母大写):School
    (2)多个单词组成:
    第一种写法(kebab-case命名):my-school,注意:注册时键名有-,要使用’‘包裹,即’my-school’
    第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)

备注:

  1. 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
  2. 可以使用name配置项指定组件在开发者工具中呈现的名字。
  1. 关于组件标签:
    第一种写法:<school></school>
    第二种写法:<school/>

备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。

  1. 一个简写方式:const school = Vue.extend(options)可简写为:const school = options,对于简写方式,vue底层有个判断,在注册组件时,如果收到的是个对象,那么他会把这个对象作为参数传入Vue.extend(),表面上你虽然没有调用Vue.extend(),但实际底层vue帮你调用了。如果不是简写,那么在注册组件时,vue直接把Vue.extend(options)拿过来用

组件的嵌套

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>组件的嵌套</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root"></div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

		//定义student组件。school组件是student组件的父组件,注意要把student组件的定义放在school组件前面
		//意思是:把子组件准备(定义)好了再去父组件那里注册
		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:'北京'
				}
			},
			//注册组件(局部)。school组件是student组件的父组件,那么把student组件注册到school组件中。
			//并且student组件只能在school组件中使用
			components:{
				student
			}
		})

		//定义hello组件
		const hello = Vue.extend({
			template:`<h1>{{msg}}</h1>`,
			data(){
				return {
					msg:'欢迎来到尚硅谷学习!'
				}
			}
		})
		
		//定义app组件
		/*
		开发中会定义个app组件,用于管理应用中所有的组件,vm管理app组件,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>
</html>

VueComponent

  1. school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
  2. 我们只需要写<school/><school></school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)
  3. 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!
  4. 关于this指向:
    (1)组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数,它们的this均是【VueComponent实例对象】。
    (2)new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数,它们的this均是【Vue实例对象】。
  5. VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm。
  6. 组件是可复用的vue实例,所以他和new Vue接收相同的选项,仅有的例外是像el这样根实例特有的选项。也就是说,vc有的功能,vm都有,vm有的功能(能通过el决定为哪个容器服务)vc就没有
  7. 一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype,也就是说,vue内部将VueComponent.prototype.__proto__指向了Vue.prototype

为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>VueComponent</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root">
			<school></school>
			<hello></hello>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false
		
		//定义school组件。Vue.extend()的返回值是个VueComponent函数
		const school = Vue.extend({
			name:'school',
			template:`
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
					<button @click="showName">点我提示学校名</button>
				</div>
			`,
			data(){//data中的数据通过数据代理放在了VueComponent实例对象身上
				return {
					name:'尚硅谷',
					address:'北京'
				}
			},
			methods: {
				showName(){
					console.log('showName',this)// this指向VueComponent实例对象,
					// 打印出来的VueComponent实例对象的内容和vue实例对象的一样
					//	区别在于:1.vue实例对象有el,VueComponent实例对象没有,
					//	2.vue实例对象的data是对象,VueComponent实例对象的data是函数,
					//	3.vue实例对象使用new Vue()创建,VueComponent实例对象使用new VueComponent()创建的
				}
			},
		})

		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>
</html>

单文件组件

main.js作为入口文件,在此创建vue实例,管控组件leader

import App from './App.vue'
new Vue({
	el:'#root',//vue接管root容器
	template:`<App></App>`,
	components:{App},
})

vue接管root容器,所以还需要新建index.html文件,index.html文件里面存放root容器:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>练习一下单文件组件的语法</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备一个容器 -->
		<div id="root"></div>
	</body>
</html>

单文件组件都以.vue为后缀,浏览器不能直接运行.vue文件,我们需要对其处理,让他变成最纯粹的js文件,浏览器就认识了。如何处理:1.自己使用webpack搭建一个工作流;2.vue脚手架

xx.vue命名规则和组件名命名规则一致
.vue文件中不能new Vue(),因为.vue文件是组件,组件不是new Vue()来的

组件:实现应用中局部功能代码和资源的集合。那么组件应该有js、HTML、css这三个结构,为此.vue文件设置了三个标签来分别存放这些内容

<template>组件的结构</template>
<script>组件交互相关代码</script>
<style>组件的样式</style>

我们之前定义组件时,是这样写的:

const school = Vue.extend({
	template:`// template中的内容放在template标签中
		<div class="demo">
			<h2>学校名称:{{schoolName}}</h2>
			<h2>学校地址:{{address}}</h2>
			<button @click="showName">点我提示学校名</button>
		</div>
	`,
	// 下面的都属于交互内容,放在script标签中
	data(){
		return {
			schoolName:'尚硅谷',
			address:'北京昌平'
		}
	},
	methods: {
		showName(){
			alert(this.schoolName)
		}
	},
})

把这部分内容分别写在对应的标签中即可

<template>
<!-- template标签中必须有个根元素 -->
	<div class="demo">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
		<button @click="showName">点我提示学校名</button>	
	</div>
</template>

<script>
	 export default {
		name:'School',
		data(){
			return {
				name:'尚硅谷',
				address:'北京昌平'
			}
		},
		methods: {
			showName(){
				alert(this.name)
			}
		},
	}
</script>

<style>
	.demo{
		background-color: orange;
	}
</style>

app组件用于汇总所有组件,相当于组件的leader,app组件由vm管控,因为.vue文件是组件,且不能被浏览器识别,所以我们需要新建main.js文件,在main.js中创建vue实例vm,让vm管控app组件

<template>
	<div>
		<!-- 3.使用组件 -->
		<School></School>
		<Student></Student>
	</div>
</template>

<script>
	//1.引入组件
	import School from './School.vue'

	export default {
		name:'App',
    	//2.注册组件
		components:{
			School
		}
	}
</script>

vue脚手架vue-cli

vue-cli

安装与使用

  1. (仅第一次执行)全局安装@vue/cli:npm install -g @vue/cli,安装好了之后,就多了个vue命令

如果下载缓慢请配置npm淘宝镜像:npm config set registry https://registry.npm.taobao.org

  1. 切换到你要创建项目的目录,然后使用命令创建项目:vue create xxx

本项目使用vue2

  1. 启动项目:npm run serve,执行该命令后,vue会直接运行main.js

脚手架结构

在这里插入图片描述
.gitignore:git的忽略文件,哪些文件不想被git管理,在这里配好
babel.config.js:Babel的控制文件
package-lock.json和package.json:只要你打开的工程符合npm规范,那么一定有这两个文件,他们是包的说明书,前者是包版本控制/锁定文件。指令介绍:

"scripts": {
    "serve": "vue-cli-service serve",/*开发时使用这个命令,让别人帮你配置服务器,帮你把东西都弄好*/
    "build": "vue-cli-service build",/*代码写完了,功能开发完了,想把整个项目变成浏览器认识的文件*/
    "lint": "vue-cli-service lint"/*对.js和.vue文件进行语法检查*/
  },

render函数

main.js作为入口文件,我们在里面创建vue实例时,写的以下代码:

new Vue({
	el:'#app',
  	render: h => h(App)
})

我们以前创建vue实例时,写的代码如下,如果我们还是写下面这个代码会报错,这是为什么呢?

new Vue({
	el:'#app',
	template:`<h1>你好啊</h1>`,
	components:{App},
})

因为我们引入vue(import Vue from 'vue')使用的是ES6模块化引入,此时引入的是..\node_modules\vue\dist\vue.runtime.esm.js,该vue残缺了模板解析器,意味着没人给你解析new Vue()中的template配置项,你必须使用render函数。

完整版的vue是..\node_modules\vue\dist\vue.js
如果你引入完整版的vue(import Vue from 'vue/dist/vue.js'),在new Vue()时可以配置template,不用配置render。

引入残缺版vue,但是我还想配置内容,就要使用render函数,render函数是vue帮你调用的,render函数将App组件放入容器中,render函数必须有返回值,返回值就是你想渲染的具体内容,render函数接收一个参数createElement,该参数createElement是个函数,借助该函数createElement渲染具体的内容。createElement('标签名','标签体内容')createElement(组件名)

render(createElement){
	return createElement('h1','你好啊')
}

没有用到this,可以写成箭头函数,箭头函数只有一个参数,可以省略(),只有一条语句并且是返回值,可以省略return和{},createElement是形参,可以用其他变量代替,如h,那么上面代码可以精简为:render: h => h('h1','你好啊')。等同于:template':<h1>你好啊</h1>'
综上:

  1. 如果你引入的时残缺版的vue,因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。那么新建vue实例时代码如下:(推荐)
import Vue from 'vue'
import App from './App.vue'//引入App组件,它是所有组件的父组件
new Vue({
	el:'#app',
  	render: h => h(App),// 这个App是个变量,他会去上面找,就找到了你上面引入的那个组件
})
  1. 如果你引入的是完整版的vue,那么新建vue实例时代码如下:
import Vue from 'vue/dist/vue.js'
import App from './App.vue'//引入App组件,它是所有组件的父组件
new Vue({
	el:'#app',
  	template:`<h1>你好啊</h1>`,
	components:{App},
})

完整版vue.js与残缺版vue.runtime.xxx.js的区别:

  1. vue.js是完整版的Vue,包含:核心功能(声明周期、处理事件等功能)+模板解析器(解析new Vue()时传入的template配置项)。
  2. vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。

为什么要设计精简版vue:如果只有完整版vue的话,在vue源码中,模板解析器代码的体积占了vue的1/3,有天你代码写完了,需要交给webpack打包,webpack打包完了,会生成一个非常大的文件,该文件中肯定包含vue,也就包含模板解析器的代码,但此时乃至以后,都用不上模板解析器了,因为在开发时,模板解析器帮我们翻译模板,但现在我们的代码写完了,借助webpack已经可以把vue翻译成.js、.css、.html了,而且那些该解析的模板都解析完了,变成了浏览器认识的文件,此时模板解析器就没什么作用了,但是他还在打包后的文件中。所以就出现了精简版vue,打包后的文件能节约一些空间。
举一个生活中的例子:假如你家要装修,要铺瓷砖,你有以下两种方案:

  1. 买瓷砖(vue核心)+买工人(模板解析器),瓷砖铺好后你得到了铺好的瓷砖和工人,你以后还要养着他们,没必要啊,他们以后没用了
  2. 买瓷砖(vue核心)+雇工人(模板解析器),瓷砖铺好后你得到了铺好的瓷砖

修改脚手架的默认配置

Vue脚手架隐藏了所有webpack相关的配置,若想查看具体的webpack配置,请执行:vue inspect > output.js
如果你采用脚手架的默认配置,以下文件/文件夹名你不能改:public、index.html、src、main.js。但是如果你非要改,在package.json保存的地方新建vue.config.js(他们两个文件要同级),vue.config.js代码如下:

//vue最终会把vue.config.js输送给webpack,webpack是基于Node的,所以vue.config.js中使用commonJS。
module.exports = {
  pages: {
    index: {
      entry: 'src/main.js',//入口
    },
  },
	lintOnSave:false, //关闭语法检查
}

修改package.json后一定要重新npm run serve
其余配置参考官方配置参考
其实,脚手架会把vue.config.js中的配置与webpack中已经写好的配置进行合并,某个配置如果vue.config.js中有,则使用vue.config.js的,否则使用webpack中已经写好的,这样程序员是碰不到核心文件的。

ref属性

需求:点击按钮获取某个DOM元素。
vue给我们提供了ref属性,用于给元素或子组件注册引用信息(id的替代者),即给DOM节点打标识。用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
用法:

  1. 打标识:<h1 ref="xxx">.....</h1> 或 <School ref="xxx"></School>
  2. 获取:this.$refs.xxx

VueComponent会把所有包含ref的节点收集到自身的$refs属性中,如下图。$refs的值是一个对象,键是ref的值,值是该ref的节点。
在这里插入图片描述
app.vue中代码如下:

<template>
	<div>
		<h1 v-text="msg" ref="title"></h1>
		<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
		<School ref="sch"/>
		<School id="sch"/>
	</div>
</template>

<script>
	//引入School组件
	import School from './components/School'

	export default {
		name:'App',
		components:{School},
		data() {
			return {
				msg:'欢迎学习Vue!'
			}
		},
		methods: {
			showDOM(){
				console.log(this.$refs.title) //真实DOM元素
				console.log(this.$refs.btn) //真实DOM元素
				console.log(this.$refs.sch) //School组件的实例对象VueComponent(vc)
        console.log(document.getElementById('sch'))//School组件对应的完整的dom结构
			}
		},
	}
</script>

school.vue中代码如下:

<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
	</div>
</template>

<script>
	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京·昌平'
			}
		},
	}
</script>

<style>
	.school{
		background-color: gray;
	}
</style>

使用ref和使用id的区别:对于传统的HTML标签来说,二者只有写法和用法上的区别,但是对于组件标签来说,<School ref="sch"/>得到的是School组件的实例对象VueComponent。如下图
在这里插入图片描述
<School id="sch"/>得到的是School组件对应的完整的dom结构,如下图
在这里插入图片描述

props配置

props:让组件接收外部传过来的数据。
父组件给子组件传数据:通过给子组件的标签写属性的方式传入数据:数据名="值",一定要加""
app.vue代码:

<template>
	<div>
		<!--:age="18":age的值是运行""中的内容得到的结果,就是数字18,""中的内容当成了表达式执行-->
		<Student name="李四" sex="" :age="18"/>
	</div>
</template>

<script>
	import Student from './components/Student'

	export default {
		name:'App',
		components:{Student}
	}
</script>

子组件使用props接收父组件传入的数据,使用props接收到的数据保存在了本组件实例对象身上,如果你在子组件实例对象身上的props中声明了一个变量,但是父组件没给你传该变量,那么多余变量的值为undefined
⚠️props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
Student.vue中代码:

<template>
	<div>
    	<!-- 在这里你相当于就在组件实例对象上了,可以使用组件实例对象身上的所有东西 -->
		<h1>{{msg}}</h1>
		<h2>学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
		<h2>学生年龄:{{myAge+1}}</h2>
		<button @click="updateAge">尝试修改收到的年龄</button>
	</div>
</template>

<script>
	export default {
		name:'Student',
		data() {
			console.log(this)
			return {
				msg:'我是一个尚硅谷的学生',
        // 接收到的props不允许改,控制台会报错,但是还是能修改成功.那么如果我们想修改接收到的数据,
        // 可以在data中定义个变量接收接收到的数据,后续改data中的变量即可
				myAge:this.age
        // name:'cara'//在data中定义个name变量,在props中定义个name变量用于接收父组件传过来的数据
        // 两个name都会被放到组件实例对象上,会发生冲突,控制台会报错,props的name变量优先级更高,
        // 以外部传进来的数据为主,也就是说props的变量优先被组件实例接收,优先被放在组件实例身上
			}
		},
		methods: {
			updateAge(){
				this.myAge++
			}
		},
    	//如果你这里声明了一个变量,但是父组件没给你传,那么多余变量的值为undefined
		// props:['name','age','sex'] 

		//接收的同时对数据进行类型限制,props写成对象,里面是一对对的键值对,键是数据名,值是JS内置对象
		/* props:{
			name:String,
			age:Number,
			sex:String
		} */

		//接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
		props:{
      		//required和default不能同时存在
			name:{
				type:String, //name的类型是字符串
				required:true, //name是必要的
			},
			age:{
				type:Number,
				default:99 //默认值
			},
			sex:{
				type:String,
				required:true
			}
		}
	}
</script>

mixin(混入)

混入:可以把多个组件共用的配置提取成一个混入对象
需求:点击“姓名”,弹窗弹出姓名。
现在我们有两个组件School和Student,他们中间有些代码一样
在这里插入图片描述
那我们可以把这些一样的代码提取到一个文件中,在School和Student使用混入直接使用这些相同的代码。新建mixin.js,将相同代码提取并暴露。注意:此处为了后续演示,我们还添加了生命周期函数和data。

export const hunhe = {
	methods: {
		showName(){
			alert(this.name)
		}
	},
	mounted() {
		console.log('你好啊!')
	},
}
export const hunhe2 = {
	data() {
		return {
			x:100,
			y:200
		}
	},
}

混入原则:

  1. 对于data中的数据、methods中的方法,混合中有的,你这里也有,那就使用你这里的,混合中有的,你这里没有,那就是用混合中的
  2. 对于生命周期函数,混合中有的和你这里有的都会用,并且优先使用混合中的。

有两种使用混入的方式:

  1. 在【用到混入的组件中引入并配置mixins】即可,如果只有Student组件使用,Student组件代码如下
<template>
	<div>
		<h2 @click="showName">学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
	</div>
</template>

<script>
	import {hunhe,hunhe2} from '../mixin'//局部引入一个hunhe

	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
				sex:'男'
			}
		},
		mixins:[hunhe,hunhe2]
	}
</script>
  1. 如果有很多组件乃至所有的组件都要用到混入中的代码,那么在main.js中全局引入并注册混入,main.js中代码:
import Vue from 'vue'//引入Vue
import App from './App.vue'//引入App
import {hunhe,hunhe2} from './mixin'//全局引入混合
//这样写,在你整个应用中,所有VC和VM都会得到这两个混合
Vue.mixin(hunhe)
Vue.mixin(hunhe2)
new Vue({//创建vm
	el:'#app',
	render: h => h(App)
})

插件

在plugins.js中定义一个插件plugins,用于增强vue。插件的本质是对象,这个对象里面必须有install函数,install函数收到的第一个参数是vue构造函数Vue,之后的参数就是插件使用者传递进来的数据。

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()应用插件,vue会帮你调用插件的install方法,并把vue构造函数作为第一个参数传入install方法。

import plugins from './plugins'//引入插件
//应用(使用)插件,一定要先应用插件,再创建vm.
Vue.use(plugins,1,2,3)

然后就可以使用插件了,如在组件中使用:

<template>
	<div>
		<h2>学校名称:{{name | mySlice}}</h2>
		<input type="text" v-fbind:value="name">
		<button @click="test">点我测试一个hello方法</button>
	</div>
</template>

<script>
	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷atguigu'
			}
		},
		methods: {
			test(){
				this.hello()
			}
		},
	}
</script>

scoped样式

以前我们写style样式时,没有加scoped,此时在每个组件中写的样式都汇总在一起了,可能出现类名冲突,在这种情况下,后【import】引入的组件的样式会覆盖掉先引入的组件的样式。为了解决这个问题,style标签添加scoped,使得样式只在【本组件】起作用,不会在子组件起作用。
写法:<style scoped>

原理:给style标签添加scoped,vue会给最外层的div添加一定标签属性,属性的值时随机生成的,再配合标签属性选择器,就完成了控制指定的div的样式

⚠️脚手架解析vue文件时,先引入,再读取配置项,最后才解析模板。
style标签中还有lang属性,用于指定写样式的语言(不写这个属性默认指定css),如css、scss、less。但是vue脚手架无法处理css之外的样式语言,所以需要提前安装插件,如less-loader,但是可能出现兼容性问题,建议安装:npm i less-loader@7

在终端输入:npm view less-loader versions可以查看less-loader的【所有】版本

总结TodoList案例

一、组件化编码流程:

  1. 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
  2. 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
    (1)一个组件在用:放在组件自身即可。
    (2)一些组件在用:放在他们共同的父组件上(状态提升)。
    (3)实现交互:从绑定事件开始。

二、props适用于:

  1. 父组件 => 子组件 通信
  2. 子组件 => 父组件 通信(子组件给父组件传数据:父组件定义个方法,方法的形参用于接收子组件传过来的数据,然后把这个方法传给子组件。子组件通过props接收到该方法,然后调用,并把数据作为实参传进去,这样,父组件该方法的形参就拿到了子组件的数据)

三、使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
四、props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
五、数据在哪里,操作数据的方法就在哪里。

浏览器本地存储webStorage

一、存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
二、浏览器端通过Window.sessionStorageWindow.localStorage属性来实现本地存储机制。
三、相关API:

  1. xxxxxStorage.setItem('key', 'value'):该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
  2. xxxxxStorage.getItem('person'):该方法接受一个键名作为参数,返回键名对应的值。
  3. xxxxxStorage.removeItem('key'):该方法接受一个键名作为参数,并把该键名从存储中删除。
  4. xxxxxStorage.clear():该方法会清空存储中的所有数据。

四、备注:

  1. SessionStorage存储的内容会随着浏览器窗口关闭而消失。
  2. LocalStorage存储的内容,需要手动清除才会消失。
  3. xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。
  4. JSON.parse(null)的结果依然是null。

五、localstorage的应用:保存搜索记录
六、浏览器中【Session会话】指的是浏览器关闭
七、SessionStorage和LocalStorage的API一样,他们最大的区别就是前者会话结束内容消失,后者需要手动删除
八、LocalStorage存储的内容什么时候消失:

  1. 你引导用户点击了某个按钮,操作了removeItem()clear()
  2. 用户自己在浏览器中清空缓存、清空数据
<!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('键','值')。键值都必须是字符串,如果值不是字符串,他会帮你调用值的toString()
                如果值是个对象,调完toString()就变成了[object Object]。
                使用JSON.stringify(字符串)将对象变成字符串
                 */
				localStorage.setItem('msg','hello!!!')
				localStorage.setItem('msg2',666)// 666.toString()
				localStorage.setItem('person',JSON.stringify(p))
			}
			function readData(){
                // localStorage.getItem('键')获取键对应的值,获取到的值是字符串,获取不到则返回null
				console.log(localStorage.getItem('msg'))
				console.log(localStorage.getItem('msg2'))

				const result = localStorage.getItem('person')
				console.log(JSON.parse(result))// JSON.parse(result)将字符串转成对象。JSON.parse(null)为null

				// console.log(localStorage.getItem('msg3'))// null
			}
			function deleteData(){
                // localStorage.removeItem('键')删除键对应的键值对
				localStorage.removeItem('msg2')
			}
			function deleteAllData(){
				localStorage.clear()// 清空
			}
		</script>
	</body>
</html>

自定义事件

一、内置事件是给HTML元素用的,自定义事件是给组件用的。
二、组件的自定义事件是一种组件间通信的方式,适用于:子组件 => 父组件
三、使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
四、绑定自定义事件:

  1. 第一种方式,在父组件中:<Demo @atguigu="test"/> 或 <Demo v-on:atguigu="test"/>
  2. 第二种方式,在父组件中:
<Demo ref="demo"/>
......
mounted(){
   this.$refs.xxx.$on('atguigu',this.test)
}

五、若想让自定义事件只能触发一次,可以使用.once修饰符,或$once方法。
六、触发自定义事件:this.$emit('atguigu',数据)
七、解绑自定义事件this.$off('atguigu')
八、组件上也可以绑定原生DOM事件,需要使用.native修饰符。
九、通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

绑定事件

需求:点击School组件的按钮,School组件将自己的数据name传给父组件App组件
思路:以前的做法(本组件代码):父组件给子组件传递一个函数,子组件调用该函数,并将数据作为参数传进去,父组件就能收到数据,在函数中进行相应的处理
App.vue中代码:

<template>
	<div class="app">
		<h1>{{msg}},学生姓名是:{{studentName}}</h1>
		<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
		<School :getSchoolName="getSchoolName"/>
	</div>
</template>

<script>
	import School from './components/School'

	export default {
		name:'App',
		components:{School},
		data() {
			return {
				msg:'你好啊!',
				studentName:''
			}
		},
		methods: {
			getSchoolName(name){
				console.log('App收到了学校名:',name)
			}
		}
</script>

School.vue中代码:

<template>
	<div>
		<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>

需求:点击Student组件的按钮,Student组件将自己的数据name传给父组件App组件

  1. 方法一:通过父组件使用==@或v-on==给子组件绑定一个自定义事件实现
    思路:子组件通过$emit发送一个事件,数据放在事件的参数中,父组件使用@或v-on监听事件,监听到后调用某函数,数据会直接作为参数传入该函数
    注意:@或v-on在谁身上,就是在给谁绑定事件
    <Student @atguigu="getStudentName"/>这行代码:@或v-on在Student这个组件标签上,所以是给Student这个组件的实例对象VueComponent身上绑定了一个事件,事件名叫atguigu,如果以后有人触发了该事件,那么getStudentName函数就会被调用。
    App.vue中代码:
<template>
	<div class="app">
		<h1>{{msg}},学生姓名是:{{studentName}}</h1>
		
		<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(使用@或v-on)-->
		<Student @atguigu="getStudentName"/>
		
    	<!-- 使用事件修饰符.once,让atguigu只有在第一次触发时会调用getStudentName -->
    	<!-- <Student @atguigu.once="getStudentName"/> -->
	</div>
</template>

<script>
	import Student from './components/Student'
	export default {
		name:'App',
		components:{School,Student},
		data() {
			return {
				msg:'你好啊!',
				studentName:''
			}
		},
		methods: {
      		// 在Student组件中触发atguigu事件时传入了参数,在getStudentName函数中会收到那些参数
      		// name,...params:第一个参数正常接收,剩余参数全部接收到数组params中
			getStudentName(name,...params){
				console.log('App收到了学生名:',name,params)
				this.studentName = name
			}
		}
	}
</script>

如何触发atguigu事件:你给谁绑定的事件,你就找谁去触发事件。Student.vue代码:

<template>
	<div class="student">
		<h2>学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
		<h2>当前求和为:{{number}}</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('想触发的事件名',后面的参数可选且都是数据)
				this.$emit('atguigu',this.name,666,888,900)
			}
		}
	}
</script>
  1. 方法二:通过父组件使用ref给子组件绑定一个自定义事件实现
    思路:App组件一挂载,就给子组件中ref为xxx的标签绑定事件,并指定监听到事件后触发什么函数,子组件通过$emit发送一个事件,数据放在事件的参数中。
    App.vue中代码:
<template>
	<div class="app">
		<h1>{{msg}},学生姓名是:{{studentName}}</h1>
		<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
		<Student ref="student"/>
	</div>
</template>

<script>
	import Student from './components/Student'
	export default {
		name:'App',
		components:{School,Student},
		data() {
			return {
				msg:'你好啊!',
				studentName:''
			}
		},
		methods: {
			getStudentName(name,...params){
				console.log('App收到了学生名:',name,params)
				this.studentName = name
			}
		},
		mounted() {
      		//下面这行代码:App组件一挂载,就给组件中ref为student的标签绑定atguigu事件,
      		//当atguigu事件被触发时,调用this.getStudentName方法
			this.$refs.student.$on('atguigu',this.getStudentName) //绑定自定义事件
			// this.$refs.student.$once('atguigu',this.getStudentName) //绑定自定义事件(一次性)

		/*
      		如果把第一行代码改为:直接把回调函数作为第二个参数传进来,回调函数是普通函数
      		this.$refs.student.$on('atguigu',function(name,...params){
				console.log('App收到了学生名:',name,params)
				console.log(this)//this指向触发事件的组件,即绑定事件的实例对象VueComponent,这里为Student实例对象
				//vue底层的设计:谁触发的事件,事件回调中的this就指向谁
				this.studentName = name
			})

			但是如果代码写为:this.$refs.student.$on('atguigu',this.getStudentName)
			Student组件实例对象触发的atguigu事件,所以this.getStudentName中的this本应该指向Student组件实例对象,
			但是getStudentName写在了methods中,且getStudentName是普通函数,那么getStudentName中的this指向本组件实例对象,
			这样一看貌似this冲突了,其实vue以后者为主,getStudentName中的this指向本组件实例对象,即App组件实例对象

			如果代码写为:直接把回调函数作为第二个参数传进来,回调函数是箭头函数
			this.$refs.student.$on('atguigu',(name,...params)=>{
				console.log('App收到了学生名:',name,params)
				console.log(this)//this指向本组件实例对象,即App组件实例对象
				this.studentName = name
			})
			箭头函数没有this,箭头函数的this是定义箭头函数时,所在的作用域指向的对象,这里是mounted函数,
			vue中规定,生命周期函数是普通函数,那么生命周期函数中的this指向本组件实例对象,即App组件实例对象
       */
		},
	}
</script>

如果把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指向触发事件的组件,即绑定事件的实例对象VueComponent,这里为Student实例对象
	//vue底层的设计:谁触发的事件,事件回调中的this就指向谁
	this.studentName = name
})

但是如果代码写为:this.$refs.student.$on('atguigu',this.getStudentName)
Student组件实例对象触发的atguigu事件,所以this.getStudentName中的this本应该指向Student组件实例对象,但是getStudentName写在了methods中,且getStudentName是普通函数,那么getStudentName中的this指向本组件实例对象,这样一看貌似this冲突了,其实vue以后者为主,getStudentName中的this指向本组件实例对象,即App组件实例对象
如果代码写为如下,即直接把回调函数作为第二个参数传进来,回调函数是箭头函数

this.$refs.student.$on('atguigu',(name,...params)=>{
	console.log('App收到了学生名:',name,params)
	console.log(this)//this指向本组件实例对象,即App组件实例对象
	this.studentName = name
})

箭头函数没有this,箭头函数的this是定义箭头函数时,所在的作用域指向的对象,这里是mounted函数,vue中规定,生命周期函数是普通函数,那么生命周期函数中的this指向本组件实例对象,即App组件实例对象

方法二适用场景:你可以在组件挂载后5s或者等ajax请求的数据回来了,再给标签绑定事件,此时mounted中的方法改为如下:

setTimeout(() => {
	this.$refs.student.$on('atguigu',this.getStudentName)
},5000)

方法一一上来就给标签绑定事件了,方法二比较灵活。

要想让自定义组件执行原生DOM点击事件,需要加事件修饰符.native,如:<Student ref="student" @click.native="show"/>。vue在解析时发现@click.native="show",知道是原生dom事件,于是他把这个click事件交给了Student组件最外层的元素,如果不加事件修饰符.native的话,他会认为@click是自定义事件

解绑事件

如何解绑:你给谁绑定的事件,你就找谁去解绑事件。

解绑一个自定义事件:this.$off('事件名称')
解绑多个自定义事件:this.$off(['事件名称1','事件名称2',...])
解绑所有的自定义事件:this.$off()

Student.vue代码:

<template>
	<div class="student">
		<h2>学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
		<h2>当前求和为:{{number}}</h2>
		<button @click="add">点我number++</button>
		<button @click="sendStudentlName">把学生名给App</button>
		<button @click="unbind">解绑atguigu事件</button>
		<button @click="death">销毁当前Student组件的实例(vc)</button>
	</div>
</template>

<script>
	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
				sex:'男',
				number:0
			}
		},
		methods: {
			add(){
				console.log('add回调被调用了')
				this.number++
			},
			sendStudentlName(){
				//触发Student组件实例身上的atguigu事件:this.$emit('想触发的事件名',后面的参数可选且都是数据)
				this.$emit('atguigu',this.name,666,888,900)
			},
			unbind(){
				this.$off('atguigu') //解绑一个自定义事件:this.$off('事件名称')
				// this.$off(['atguigu','demo']) //解绑多个自定义事件:this.$off(['事件名称1','事件名称2',...])
				// this.$off() //解绑所有的自定义事件:this.$off()
			},
			death(){
				this.$destroy() //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效,
        // 但是原生的DOM事件(点击事件click)还能用。
			}
		},
	}
</script>

注意:如果销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件(unbind、sendStudentlName)全都不奏效,但是原生的DOM事件(点击事件click)还能用,即点击【点我number++】按钮,还能调用add()触发number++
同理,如果VM被销毁了,他所有子组件都没了,所有子组件中的自定义事件也都被销毁了,但是原生的DOM事件(点击事件click)还能用。

全局事件总线

一、全局事件总线是一种组件间通信的方式,适用于任意组件间通信
二、安装全局事件总线:在main.js中创建vue实例时,将当前vue实例绑定到Vue.prototype.$bus

new Vue({
	......
	beforeCreate() {
		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
	},
    ......
}) 

三、使用事件总线:

  1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){
  demo(data){......}
}
......
mounted() {
  this.$bus.$on('xxxx',this.demo)
}
  1. 提供数据:this.$bus.$emit('xxxx',数据)

四、最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

需求:点击Student组件的按钮,把Student组件的name传给School组件。

  1. 首先在创建vue实例是安装全局事件总线,main.js中的代码:
import Vue from 'vue'//引入Vue
import App from './App.vue'//引入App
Vue.config.productionTip = false//关闭Vue的生产提示
new Vue({//创建vm
	el:'#app',
	render: h => h(App),
	beforeCreate() {
		Vue.prototype.$bus = this //安装全局事件总线
	},
})
  1. 然后在Student组件中发送数据,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:'男',
			}
		},
		methods: {
			sendStudentName(){
				this.$bus.$emit('hello',this.name)
			}
		},
	}
</script>
  1. 最后在School组件中接收数据,并在School组件销毁前解绑事件。School.vue中的代码:
<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
	</div>
</template>

<script>
	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
			}
		},
		mounted() {
			this.$bus.$on('hello',(data)=>{
				console.log('我是School组件,收到了数据',data)
			})
		},
		beforeDestroy() {//组件销毁前解绑事件
			this.$bus.$off('hello')
		},
	}
</script>

消息订阅与发布

一、消息订阅与发布是一种组件间通信的方式,适用于任意组件间通信
二、思想:A组件想用C组件的消息,A组件是消息使用者,所以他订阅消息,C组件是消息提供者,所以他发布消息。A组件订阅名为demo的消息,并指定一个回调test。C组件会发布demo消息,发布时带上数据,此时A组件就会收到demo消息并调用test,数据作为参数传进了test函数中。
三、使用步骤:原生JS无法轻松实现消息订阅与发布,所以我们需要借助第三方库pubsub-js(你也可以用别的库,消息订阅与发布只是一个理念,有很多库把这个理念落到了实处),pubsub-js可以在任何框架中,轻松实现消息订阅与发布。

  1. 在项目终端安装pubsub-js:npm i pubsub-js
  2. 引入: import pubsub from 'pubsub-js'

四、接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

methods(){
  demo(data){......}
}
......
mounted() {
  this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
}

五、提供数据:pubsub.publish('xxx',数据)
六、最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。
七、需求:点击Student组件的按钮,把Student组件的name传给School组件。
八、思路:School组件接收数据,Student组件发送数据,双方都要用到pubsub-js,在两个组件中引入pubsub-js,引入的pubsub-js是个对象,身上有很多有用的方法。

  1. Student组件发布消息,Student组件中的代码:
<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:'男',
			}
		},
		methods: {
			sendStudentName(){
        		//发布消息:pubsub.publish('消息名',数据)
				pubsub.publish('hello',666)
			}
		},
	}
</script>
  1. School组件接收消息,School组件代码如下:
<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
	</div>
</template>

<script>
	import pubsub from 'pubsub-js'// 引入pubsub-js,pubsub是个对象,身上有很多有用的方法
	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
			}
		},
    // methods: {
    //   pub(msgName, data) {
    //     console.log(this)// this指向本组件实例,即School组件
    //   }
    // },
		mounted() {
      		/*
      		订阅消息:pubsub.subscribe('消息名',回调函数)
      		回调函数接收两个参数,分别是消息名和数据(规定的)
     	 	每次订阅消息都会获得一个订阅ID,有点像定时器
     	 	
      		this.pubId = pubsub.subscribe('hello',function(msgName,data){
				console.log(this)// this指向undefined,因为你这里使用的是第三方库
			})

			也可以将回调函数抽离成方法放在methods中,订阅时传递方法名即可,此时回调函数的this依旧指向本组件实例,即School组件
			this.pubId = pubsub.subscribe('hello',this.pub)
       */
			this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
				console.log(this)// this指向本组件实例,即School组件
				console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
			})
		},
		beforeDestroy() {
      		//通过ID取消订阅:pubsub.unsubscribe(ID)
			pubsub.unsubscribe(this.pubId)
		},
	}
</script>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值