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

目录

  • 生命周期/生命周期回调函数/生命周期函数/生命周期钩子
    • new Vue的template配置项
  • 组件
    • 非单文件组件
      • 基本使用
      • 组件的嵌套
      • VueComponent
    • 单文件组件
  • vue脚手架vue-cli
    • 安装与使用
    • 脚手架结构
    • render函数
    • 修改脚手架的默认配置
  • ref属性
  • props配置
  • mixin(混入)
  • 插件
  • scoped样式
  • 总结TodoList案例
  • 浏览器本地存储webStorage
  • 自定义事件
    • 绑定事件
    • 解绑事件
  • 全局事件总线
  • 消息订阅与发布

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

一、介绍:

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

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

二、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,二者的区别:

    如果将root容器作为template,会解析root容器的:x得到

    如果将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,二者的区别:


如果将root容器作为template,会解析root容器的:x得到

如果将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中的内容,没有

了,那么这样,你原本配置的:x="n"也就没有了。


<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:`
			 	

当前的n值是:{{n}}

`
, 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. 使用组件(写组件标签):

非单文件组件

基本使用


<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">
			
			
			<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:`
				

学校名称:{{schoolName}}

学校地址:{{address}}

`
, // el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。 data(){// data一定要写成函数形式,并且是普通函数 return { schoolName:'尚硅谷', address:'北京昌平' } }, methods: { showName(){ alert(this.schoolName) } }, }) //第一步:创建hello组件 const hello = Vue.extend({ template:`

你好啊!{{name}}

`
, 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. 关于组件标签:
    第一种写法:
    第二种写法:

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

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

组件的嵌套


<html>
	<head>
		<meta charset="UTF-8" />
		<title>组件的嵌套title>
		
		<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:`
				

学生姓名:{{name}}

学生年龄:{{age}}

`
, data(){ return { name:'尚硅谷', age:18 } } }) //定义school组件 const school = Vue.extend({ name:'school', template:`

学校名称:{{name}}

学校地址:{{address}}

`
, data(){ return { name:'尚硅谷', address:'北京' } }, //注册组件(局部)。school组件是student组件的父组件,那么把student组件注册到school组件中。 //并且student组件只能在school组件中使用 components:{ student } }) //定义hello组件 const hello = Vue.extend({ template:`

{{msg}}

`
, data(){ return { msg:'欢迎来到尚硅谷学习!' } } }) //定义app组件 /* 开发中会定义个app组件,用于管理应用中所有的组件,vm管理app组件,app组件管理别的组件 */ const app = Vue.extend({ template:`
`
, components:{ school, hello } }) //创建vm new Vue({ template:'', el:'#root', //注册组件(局部) components:{app} })
script> html>

VueComponent

  1. school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
  2. 我们只需要写,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原型上的属性、方法。


<html>
	<head>
		<meta charset="UTF-8" />
		<title>VueComponenttitle>
		<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:`
				

学校名称:{{name}}

学校地址:{{address}}

`
, 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:`atguigu` }) //定义hello组件 const hello = Vue.extend({ template:`

{{msg}}

`
, 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:``,
	components:{App},
})

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


<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标签中
		

学校名称:{{schoolName}}

学校地址:{{address}}

`
, // 下面的都属于交互内容,放在script标签中 data(){ return { schoolName:'尚硅谷', address:'北京昌平' } }, methods: { showName(){ alert(this.schoolName) } }, })

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

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

脚手架结构

vue -【生命周期】-【组件】-【脚手架】-【ref】-【props】-【混入mixin】-【插件】-【scoped】-【webStorage】-【自定义事件】-【全局事件总线】-【消息订阅与发布】_第2张图片
.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:`

你好啊

`
, 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':

你好啊

'
综上:

  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:`

你好啊

`
, 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. 打标识:

    .....

  2. 获取:this.$refs.xxx

VueComponent会把所有包含ref的节点收集到自身的$refs属性中,如下图。$refs的值是一个对象,键是ref的值,值是该ref的节点。
vue -【生命周期】-【组件】-【脚手架】-【ref】-【props】-【混入mixin】-【插件】-【scoped】-【webStorage】-【自定义事件】-【全局事件总线】-【消息订阅与发布】_第3张图片
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组件的实例对象VueComponent。如下图
在这里插入图片描述
得到的是School组件对应的完整的dom结构,如下图
vue -【生命周期】-【组件】-【脚手架】-【ref】-【props】-【混入mixin】-【插件】-【scoped】-【webStorage】-【自定义事件】-【全局事件总线】-【消息订阅与发布】_第4张图片

props配置

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

<template>
	<div>
		
		<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,他们中间有些代码一样
vue -【生命周期】-【组件】-【脚手架】-【ref】-【props】-【混入mixin】-【插件】-【scoped】-【webStorage】-【自定义事件】-【全局事件总线】-【消息订阅与发布】_第5张图片
那我们可以把这些一样的代码提取到一个文件中,在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,使得样式只在【本组件】起作用,不会在子组件起作用。
写法: