Vue其二--组件化编程

传统方式编写应用:

css、js、html文件分开来写:

组件化编程

 如上:组件当中可以用除css\js\html以外的文件

 ​​​​​如下:组件是可以嵌套的

 组件:实现应用中局部功能代码和资源的集合

 前言:组件分类

非单文件组件

一个文件中包含n个组件

 一个html文件当中可以有多个组件

单文件组件(常用)

一个文件中只包含1个组件

 一个.vue文件中只有一个组件

 一、Vue当中如何使用一个组件:

1、组件的基本使用

步骤:创建组件-----注册(局部或全局)组件-----编写组件标签

 一、如何定义一个组件?

        使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;

        区别如下:

               1.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。

               2.data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。

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

 二、如何注册组件?

         1.局部注册:靠new Vue的时候传入components选项

         2.全局注册:靠Vue.component('组件名',组件)

三、编写组件标签:

          <school></school>

以下先以非单文件组件为例(了解原理,方便学习单文件组件即可,不常用)

例如页面中两部分,分别显示学生和学校的信息,所以说可以分别创建学生和学校两个组件

        组件就是一块砖,哪里需要哪里搬,组件可以为任何需要的地方服务。所以说其格式和new vue所常见的vm实例基本内容一致,但是没有el配置项。

        组件中的data不能像vm当中写成对象,而要写成函数形式,在函数当中返回数据

<div id="root">
	<!-- 第三步:编写组件标签 -->
	<school></school>
	<hr>
	<!-- 第三步:编写组件标签 -->
    <!-- 下面写两个student组件,修改一个学生的年龄,对另外一个组件当中也没有影响,互不干扰 -->
	<student></student>
    <student></student>
    <!-- 全局组件hello在root和root2当中都可以使用 -->
    <hello></hello>
</div>

<div id="root2">
	<hello></hello>
</div>

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

	//第一步:创建school组件
	const school = Vue.extend({
		template:`
			<div class="demo">
				<h2>学校名称:{{schoolName}}</h2>
				<h2>学校地址:{{address}}</h2>
				<button @click="showName">点我提示学校名</button>	
			</div>
		`,
		// el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
		data(){
			return {
				schoolName:'尚硅谷',
				address:'北京昌平'
			}
		},
		methods: {
			showName(){
				alert(this.schoolName)
			}
		},
	})
	//第一步:创建student组件
	const student = Vue.extend({
		template:`
			<div>
				<h2>学生姓名:{{studentName}}</h2>
				<h2>学生年龄:{{age}}</h2>
			</div>
		`,
		data(){
			return {
				studentName:'张三',
				age:18
			}
		}
	})
		
	//第一步:创建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:{
			school,
			student
		}
	})
	new Vue({
		el:'#root2',
	})
</script>

代码注意事项:

 除此以外还应该注意:创建student组件使用关键词是Vue.extend,页面的div代码是写在template当中,且template当中只能有一个div

2、嵌套组件

 在下面代码中,可以看见,app处于vm之下管理所有的组件。

现在我们要把student组件写在school组件当中,代码如下

<body>
	<!-- 准备好一个容器-->
	<div id="root">
		
	</div>
</body>

<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
    //这里直接把app组件写在vm的template当中,上面的div内就不需要内容了,注册app同理
	new Vue({
		template:'<app></app>',
		el:'#root',
		//注册组件(局部)
		components:{app}
	})
</script>

3、VueComponent

关于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。

 对上面的解释

1、如下图直接输出组件,可以在vue.js当中找到VueComponent构造函数

 2、因为school是一个构造函数,所以说可以创建其实例对象。解析模板时的<school></school>时vue自动new的VueComponent(options),其中options是配置对象即定义组件的name、template、data等也被vue自动传入了

 3、即定义且注册、使用了两个组件school和hello,虽然着两个组件都是VueComponent,且VueComponent都在名称和功能一样,但是是两个不同的VueComponent。类似于一个类的两个对象。且在vue.js源码当中可以看见每次都是重新调用VueComponent去生成

4、直接log输出看看就知道了,会发现vue和VueComponent实例对象基本都一样的

        但是vm和vc的区别,一个是vm里面有el,而vc里面没有;另外就是vc当中data必须是函数,而vm当中的data可以说对象、函数

5、注意是一个vm内部可以有多个vc,即一个vue实例对象可以嵌套多个组件,即管理多个组件

 4、分析Vue与VueComponent的关系

建议参考http://t.csdn.cn/M2UBf的原型对象一起看

1.一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype

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

5、单文件组件

这里主要讲解代码如何写,至于配置是需要的脚手架,不然浏览器不能识别等下一节讲

组件当中主要写html、js、css代码

分别对应的标签是<template></template>       <script></script>          <style></style>

这里也看出非单文件组件的缺点之一,即写css代码还需要另外在css文件中或者在另外的style标签中写样式。并没有把html、js、css组件化

html和css代码都是和非单文件相同

js的区别:需要了解es6模块化的暴露,其他文件里面还需要引入vue

        一般是采用下面的方法:默认暴露

        1、不需要中间量school;

        2、Vue.extend可以省略(非单中也可以)

  

 然后就可以在App.vue中引用

 具体代码可以直接看源码,了解各个文件之间的联系

 6、Vue脚手架

Vue CLI

 先选择vue2回车即可

 运行npm run serve

 下面是脚手架常见的项目大概文件夹,还有路由这没说

 注意public和assets,分别是icon图标和index.html文件,一个是正常图片

 

 二、Vue技术

1、Render()函数

引入的vue是精简(残缺)的vue,缺少模板解析器,所以说无法解析main.js文件当中的vm实例里面的template组件内的内容,见下

 下面需要补充箭头函数ES6(四)形式简洁 => 箭头函数_半糖冰的博客-CSDN博客

render原始形态

       vue包含核心功能+模板解析器,但是写在一起的话即需要一致使用完整版vue,会导致代码在打包时生成的文件夹过大,因为开发时模板解析器会帮助我们翻译模板,但是打包时使用的webpack已经把.vue变成.js,即不需要模板解析器了,js能直接被浏览器认识,所以说打包时没有打包模板解析器,只打包了核心部分。

核心功能:生命周期、处理事件等等、

模板解析器:template等模板需要被解析

注意,在main.js当中的vm实例里面的template组建不能写。但是在.vue文件当中冉冉可以有template,因为package.json里面21行有引入解析vue文件的标签的库

 2、ref属性:

返回DOM元素(id和ref都可) 和 组件实例对象(只有ref可以使用)

1. 被用来给元素或子组件注册引用信息(id的替代者,因dom中document.getel...也可以)

2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)

3. 使用方式:

    1. 打标识:<h1 ref="xxx">.....</h1>    或    <School ref="xxx"></School>

    2. 获取:   this.$refs.xxx

 3、props配置项

        页面上有了基本的格式,比如说student组件当中包含姓名年龄等。渲染的数据之前是直接来自vm实例当中的data里面,但是写在vm里的数据难以修改,且如果需要重复使用该组件但是组件的数据有不相同的话数据还是从外部传入。即对同一组件在复用时如何渲染不同的数据。

以下为Student.vue组件代码

<template>
	<div>
		<h1>{{msg}}</h1>
		<h2>学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
		<h2>学生年龄:{{age}}</h2>
	</div>
</template>

<script>
	export default {
		name:'Student',
		data() {
			console.log(this)
			return {
				msg:'我是一个尚硅谷的学生',   //写在data里的表示所有实例化的组件都有这个数据
			}
		},
		//简单声明接收,即最省略最常用的写法(下面的参数要和app.vue里对应,顺序无所谓)
		props:['name','age','sex'] 
	}
</script>

补充:如果有需要修改数据的情况出现,以age为例,让年龄统一  +1。第一张图已经把age因为字符串格式问题解决了。

重点:先准备props,然后再去配置数据。优先级是外部数据高于data内部

下面是对props的补充,代码文件有详情

props的补充:

    1. 第一种方式(只接收):```props:['name','age'] ```

    2. 第二种方式(限制类型):```props:{

                                                                name:String

                                                                 'age':Number

                                                        }

    3. 第三种方式(限制类型、限制必要性、指定默认值):                 

        props:{

            name:{

                type:String, //name的类型是字符串

                required:true, //name是必要不可少的

            },

            age:{

                type:Number,

                default:99 //默认值

            }

        }

备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据(源代码中有例子)。

 4、mixin混合(混合)

即两个组件共享一个相同的配置提取成一个混入对象,可以单独写一个js文件然后引用(通常在main.js内)。

注意是混合,这个优先级低,组件里面有了,以组件内有的优先,没有的添加到组件的功能当中。

第一步定义混合:

    {

        data(){....},  (下面例子里面没写data,源代码有这个例子)

        methods:{....}

    }

 第二步使用混入:

    ​ 全局混入:```Vue.mixin(xxx)```

    ​ 局部混入:```mixins:['xxx']  (在组件内import后,再引入)```

背景:school和student组件都有一个点击名称弹窗显示的方法如下,我们可以单独写出这部分再用

然后我们删除组件相关代码做出如下修改

引入可以直接在main.js全局引用即可

 5、插件

插件可以提供很多功能方便在其他地方使用

1. 功能:用于增强Vue

2. 本质:包含install方法的一个对象

                install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

3. 定义插件:

    对象.install = function (Vue, options) {

        // 1. 添加全局过滤器

        // 2. 添加全局指令

        // 3. 配置全局混入(合)

        // 4. 添加实例方法

    }

4. 使用插件:```Vue.use()```

 举例:在新建js文件plugins.js中,定义了全局过滤器、全局指令、定义混入、给Vue原型添加方法。完成后这些东西都可以直接在其他地方使用。

 引入过后再引用插件,可以多写写Vue.use(),引用插件数量无上限

 install内的参数解析:

在main.js当中使用插件时,可以传入参数如下:

区别下mixin混合和插件在定义和引入时的区别:

混合:export const hunhe = {...}

插件:export default{....}

所以说引用时也有不同上面有

 6、scoped样式

scope:范围,作用域,界限

1. 作用:让样式在局部生效,防止冲突。

2. 写法:```<style scoped>```

        以样式为例引入该知识点,在school和student组件当中style中设置其背景色,即每个组件的背景色都写在组件内部,容易类名冲突(即div的class属性如果不同组件一样了会冲突,冲突顺序是在app.vue中引入组件时后面的会覆盖前面的)。

容易冲突的例子如下:

 容易发现上面两个组件class相同导致冲突,注意优先级问题

 解决办法:这样即使有冲突也不会有问题

          例外:App.vue里面如果有style则他是所有组件的sytle,如果在这里用scoped的话所有组件并不是可以用的。App.vue的template中的组件就不能用了,但是其他的能用

 补充知识点:脚手架安装less,可能有更新,自己搜即可

7、浏览器本地存储Storage

本地存储localStorage:如下唯品会,即使不登录账号,也能够记录搜索历史,且关闭浏览器或者页面后搜索历史仍然在

会话存储sessionStorage:和本地存储唯一区别是退出浏览器后数据丢失

不管是 localStorage,还是 sessionStorage,可使用的API都相同,常用的有如下几个(以localStorage为例):

  • 保存数据:localStorage.setItem(key,value);
  • 读取数据:localStorage.getItem(key);
  • 删除单个数据:localStorage.removeItem(key);
  • 删除所有数据:localStorage.clear();
  • 得到某个索引的key:localStorage.key(index);

本地存储localStorage:

<!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(){
				//setItem会把非字符串的number和对象都被ToString转化为字符串,number还好,但是下面的p对象变成字符串便读不出来值
				//JSON.stringify() 方法将 JavaScript 对象转换为字符串,但是能够体现其中内容(不同于ToString())
				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'))
				//JSON.parse() 方法将数据转换为 JavaScript 对象,否则读取出来也是单纯的字符串
				const result = localStorage.getItem('person')
				console.log(JSON.parse(result))
				// console.log(localStorage.getItem('msg3'))读取没有的值显示null
			}

			function deleteData(){
				localStorage.removeItem('msg2')
			}

			function deleteAllData(){
				localStorage.clear()
			}
		</script>
	</body>
</html>

会话存储SessionStorage:

将上面代码的LocalStorage换成SessionStorage即可(Ctrl+h:当前文件下替换

除了浏览器关闭会清空数据外与本地存储没有区别

8、组件自定义事件

1、绑定

js内置事件:click、keyup.....都是给HTML元素使用的

组件自定义事件:如自己写atguigu、peiqi等事件,注意是给组件使用的

 对于第一个按钮的实现,之前已经讲过,此处简单提下即可

 重点是实现第二个按钮,即组件自定义事件(以Student组件为例):

  • 使用 $on(eventName) 监听事件
  • 使用 $emit(eventName) 触发事件

 组件自定义方法二:利用ref(vue3被禁用了???)

 这种方法更灵活,即可以在mounted里面写东西如

 如果student组件传输的数据不止name一个,有很多个数据时。我们还可以把这些参数整合成为一个对象,或者使用下面的es6语法(常用)(下面分别是Student组件和App组件,用的是第一种方法):

//只是把上面的student组件内多加了传入App的数据
methods: {
	sendStudentName(){		
		this.$emit('atguigu',this.name,666,888,999)
	}

}
//App组件内
//(name,...params)表示对于传如的参数,无论多少个,第一个始终是name,其余全部存入数组a[]
getStudentName(name,...params){
	console.log('App收到了学生名:',name,params)
}

 2、解绑

并非单纯的不使用组件自定义事件,而是要把组件绑定的自定义事件的绑定取消。以上面的student内的自定义事件atguigu为例,在student组件内

关于销毁和Vue生命周期的补充:

自定义事件解绑:$off                   vc和vm销毁:$destroy

解绑只有Student实例的自定义事件失效

销毁当前Student组件的实例,销毁后所有Student实例的自定义事件和原生事件全都不奏效

销毁当前Root即vm,上面两个也都不可以使用

<template>
	<div class="student">
		<h2>学生姓名:{{name}}</h2> h2>学生性别:{{sex}}</h2> <h2>当前求和为:{{number}}</h2>
		<button @click="add">点我number++</button>
		<button @click="sendStudentName">把学生名给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++
			},
			sendStudentName(){
				this.$emit('atguigu',this.name,666,888,999)
			},
			unBind(){
				this.$off('atguigu') //解绑一个自定义事件
			},
			death(){
				this.$destroy() 
			}
		}
	}
</script>

 下面是页面挂载完成3s后销毁vm的代码,其余和刚才一样

 3、总结

3、1  this指向问题

实现点击按钮将名称显示在下图中如下位置

 

 现在重点是使用ref来实现该案例

Vue原则:在method内的函数且写作普通函数形式,其中的this指向该method所在组件实例

普通函数原则:那个组件触发了事件,那么事件回调当中的this就指向谁

箭头函数原则:指向父级组件实例对象

事件的回调需要匹配在method对象中,最终会在vm上

method配置的函数,不要用箭头函数,否则this就不是vm

method配置的函数,都是被vue所管理的函数,this的指向就是vm 或 组件实例对象

 直接把mouted函数写成这样

mounted() {
	this.$refs.student.$on('atguigu',function (name,...params) {
		console.log('App收到了学生名:',name,params)
		this.studentName = name
        console.log(this)
	})
}

在控制台发现

 根据上面的普通函数原则知道,此处是student触发atguigu事件,所以说this指向student

推荐写法:要么就写函数在methoed里面(最好),要么mounted里面写箭头函数如下

此时this就指向父级App

 3、2  组件内使用原生事件

vue3已经取消native,直接使用@click即可

组件内绑定的事件默认自定义事件,即使是@click=“show”这种一眼原生

@click.native=“show”,加修饰符:native即可

 9、全局事件总线(推荐)

上面已经学习了props:父传子。组件自定义事件:子传父

现在利用组件自定义事件技术来实现全局事件总线,即父子、子父、甚至是爷孙辈的数据任意传递(父子仍然是props最快)

Vue事件总线(EventBus)、$on、$emit、$off_$eventbus_奥特曼 的博客-CSDN博客

总线:bus(暂时用x替代)

可以实现任意组件间的通信(父、子间仍然是props最方便),下面是原理图

 仍然是下面的例子

 1、首先是写让所有组件(vc)都可见的X,所有的vc都是VueComponent所new出来的,复习之前的(单文件组件)知道每个组件都是新的VueComponent,所以说不可以,记住之前的原型图,应该直接写在Vue原型对象上,他是唯一的,且vc和vm都能够访问它

下图中vc组件通过黄色的线访问vue原型对象的X

因为是在main.js引入的Vue,所以说在main.js里面操作Vue,给他加上X

 然后就可以正常在其他组件访问X

mounted是页面挂载完成自动运行

然后是考虑调用$on,$off,$emit的问题,X就是普通Object类型的对象,身上是没有$.....的方法的,然后直接x.$on因为x没有,就找到x的Object原型,仍然没有,所以说不能直接将X定义为对象

$on、$off、$emit都在Vue的原型对象上,所以说组件实例对象(this)能够直接调用$on,$off,$emit。为了X能够找到这三个方法,所以说x也应该定义为vc或者vm,这样就可以找到Vue的原型对象,然后找到Vue原型对象上的这三个方法

//回看之前57集,Demo是Vuecomponent,d就是组件实例对象即vc
const Demo = Vue.extend({})
const d = new Demo()
Vue.prototype.x = d 

但是仍然可以改进,下面是标准写法(上面是vc写法,下面是vm写法):

//beforeCreate是生命周期钩子,此时的this是当前new出来的vm
//此时模板还没有解析,数据监测和代理都没有完成,所以说这时先把原型上面需要放的放好即x
new Vue({
	el:'#app',
	render: h => h(App),
	beforeCreate() {
		Vue.prototype.x= this //安装全局事件总线,this就是当前未完全体的vm
	},
})

实际上x需要写成$bus更常见,$是Api自带的无实际含义

 所以说为了实现Student向School数据学生名

## 全局事件总线(GlobalEventBus)

1. 一种组件间通信的方式,适用于<span style="color:red">任意组件间通信</span>。

2. 安装全局事件总线:

   ```js

   new Vue({

      ......

      beforeCreate() {

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

      },

       ......

   })

   ```

3. 使用事件总线:

   1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的<span style="color:red">回调留在A组件自身。</span>

      ```js

      methods(){

        demo(data){......}

      }

      ......

      mounted() {

        this.$bus.$on('xxxx',this.demo)

      }

      ```

   2. 提供数据:```this.$bus.$emit('xxxx',数据)```

4. 最好在beforeDestroy钩子中,用$off去解绑<span style="color:red">当前组件所用到的</span>事件。

 10、消息订阅与发布

同样可以在组件间传输数据

1、订阅消息:消息名(的手机号)

2、发布消息:消息内容(需要发给我的内容)

 A组件内定阅一个消息demo,随后指定其回调为test

C组件发布消息名demo,携带数据666,因为A定阅了demo,所以说test会触发,666以参数的形式被传到test中,有许多消息订阅相关js库,常用为pubsub-js

 和上面全局事件总线一样,把学生名从Student传到School当中

 安装完js库后,再引入。记住School和Student当中都要引入。

import pubsub from 'pubsub-js'                pubsub下面会用到

上面的全局总线是在school组件挂载完毕就给$bus绑定hello事件;现在是挂载完毕了立刻去定阅一个消息。

下面是School’组件的js代码,被注释掉的是全局事件总线,注意区别

subscribe:订阅                      unsubscribe:取消订阅                                publish:发布

//hello是定阅消息名,msgName消息名(也是hello),data是接收返回数据的参数

pubsub.subscribe(‘hello’,function(msgName,data){

        函数体

})

//在组件销毁后同样需要取消定阅,beforeDestroy之前有讲解

//pubsub.unsubscribe(id)的参数是每次定阅的消息的ID(自动生成,代码中使用pubId接收此ID,取消定阅也是通过此ID)
pubsub.unsubscribe(this.pubId)

总结:接收方subscribe订阅消息,发送方publish消息。订阅和发布的消息名保持一致。订阅方需要设置回调函数,发布方需要写数据

//School当中订阅消息
mounted() {
    // console.log('School',this)
    /* this.$bus.$on('hello',(data)=>{
	       console.log('我是School组件,收到了数据',data)
        }) */
	this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
		console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
	})
},
beforeDestroy() {
	// this.$bus.$off('hello')
	pubsub.unsubscribe(this.pubId)
}
//Student当中发布消息
methods: {
	sendStudentName(){
		// this.$bus.$emit('hello',this.name)
		pubsub.publish('hello',666)
	}
}

 补充:

消息订阅如果不用箭头函数写输出的this为undefine,因为需要向vm的data传数据,所以说写成箭头函数形式,下面的不推荐

this.pubId = pubsub.subscribe('hello',function(msgName,data){
    console.log(this)//undefine
	console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
})

11、过渡与动画

CSS3 @keyframes 规则 | 菜鸟教程

理解v-enter 与v-leave 周期与范围,vue的过渡动画_v-enter-active_zZhiqg^的博客-CSDN博客

变换(transform)——移动(translate)、旋转(rotate)、缩放(scale)、扭曲(skew)、基点变换(transform-origin)、动画(animation)_transform: translatex_钟意冉冉的博客-CSDN博客

 下面来实现点击按钮实现Dom元素的隐藏和显示,并且会有相关的动画

QQ录屏20230827201652

1、 动画

 下面我们提到transition需要有自己的name属性,所以说我们写多个时,分别取两个名字,赋予其不同的动画

<template>
	<div>
		<button @click="isShow = !isShow">显示/隐藏</button>
		<!-- 将需要有动画效果的部分用transition(过渡)标签包裹起来(trans..标签不显示在页面上)。Vue会自动在合适的时候加上come和go属性 -->
		<!-- appear默认为true真,表示进入页面加载其内部的h1标签(还未点击按钮)就触发动画 -->
		<!-- 需要有name,不然控件多了全都默认v容易乱 -->
        <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是上面transition标签名(默认是v)‘-enter-active’是固定的表示进入的样式,linear匀速 */
	.hello-enter-active{
		animation: atguigu 0.5s linear;
	}
	/* -leave-active表示离开时的样式,reverse反转 */
	.hello-leave-active{
		animation: atguigu 0.5s linear reverse;
	}
    /* keyframes和transform都看下,下面是混合用的,注意动画名atguigu在上面使用 */
	/* form...to...从哪里到哪里,即下面动画是水平-100到0,即屏幕外到屏幕内 */
	@keyframes atguigu {
		from{
			transform: translateX(-100%);
		}
		to{
			transform: translateX(0px);
		}
	}
</style>

2、过渡

上面我们是用动画实现,现在我们使用过渡来实现

重点看一下上面的vue动画周期的链接,理解其有多少阶段

v-enter和v-leave都是一瞬间的事情,然后就分别托付给了v-enter-to和v-leave-to

下面的进入阶段合离开阶段中间就是页面稳定的状态(无动画)

1、下面是新建Test2的部分代码,和Test的不同在于用过渡实现(只要这里的代码不相同)

2、注意因为过渡是一个循环,所以说进入的起点、离开的终点translateX值相同,写在一起

3、和动画一样,也需要设置其过渡的持续时间0.5s和liner匀速,由上面的图可以知道enter-active是包括进入的全过程的,所以说像下面这样写即可

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

多个元素需要有同样的过渡效果的写法

1、<transition>仅仅可以被使用在一个单独的元素上,如果有多个请使用<transition-group>

即对于一个列表,只能使用<transition-group>

2、使用<transition-group>时,里面的每一个元素都应该有一个唯一的‘key’值 

3、使用<transition-group>的优点是对于多个元素能够分别控制

 代码如下,我们添加了一个“尚硅谷!”元素

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

 下面的形式也可以实现两个元素有相同的过渡动画,但是如果需求是两个元素互斥或者其他情况便只能使用上面的,因为下面这个这两个元素完全相同。

例如两个元素每次只能出现一个时直接如下右图即可

3、控制台新开终端安装常用动画库之一:Animate.css | A cross-browser library of CSS animations.

npm install animate.css --save

 安装后必须引入,引入的是样式不是一个js,直接import即可,不用.... from ....

import 'animate.css';

 安装引入Animate.css后,将Animate__animated类以及任何动画名称添加到元素中(不要忘记Animate__前缀),记住直接把下面的class名写入<transition-group>的name里面

<h1 class="animate__animated animate__bounce">An animated element</h1>

具体代码如下所示,重点是配置name为上面的固定写法;enter-active-class=" " 和 leave-active-class=" "分别是进入和离开时的,直接在Animate.css | A cross-browser library of CSS animations.右侧选中想要的复制即可

<template>
	<div>
		<button @click="isShow = !isShow">显示/隐藏</button>
		<transition-group 
			appear
			name="animate__animated animate__bounce" 
			enter-active-class="animate__swing"
			leave-active-class="animate__backOutUp"
		>
			<h1 v-show="!isShow" key="1">你好啊!</h1>
			<h1 v-show="isShow" key="2">尚硅谷!</h1>
		</transition-group>
	</div>
</template>

12、配置代理

看完后去看Ajax的用户搜索案例

先启动文档中server1和server2,分别是学生数据和汽车数据,端口5000和5001

1、方式一(有缺陷)

发送ajax请求方法:

1、原生的Ajax当中的xhr:new XMLHttpRequest()         xhr.open()   xhr.send()

2、封装了xhr的jQuery     $.get    $.post

3、vue常用的axios:相较于jQuery的优势有promise方式风格,支持请求响应拦截器

4、fetch:不同于jQuery、axios,fetch不是封装的chr,而是和xhr同一级的,和xhr一样是window原生的,也是promise风格。但是需要两层then,且ie兼容性差

 首先需要下载并且引入axios

控制台安装:npm i axios

App.vue引入:import axios from 'axios'

<div>
	<button @click="getStudents">获取学生信息</button>
</div>
<script>
	import axios from 'axios';
	export default {
		name:'App',
		methods: {
			getStudents(){
				axios.get('http://localhost:5001/cars').then(
					response => {
						console.log('请求成功',response.data);
					},
					error => {
						console.log('请求失败',error.message);
					}
				)
			}
		}
	}
</script>

 点击上面生成页面按钮后发现报错

Access to XMLHttpRequest at 'http://localhost:5001/cars' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

跨域:

协议名、主机名、端口号必须一致,即http   localhost    8080三个必须一致

但是我们之前启动服务器可以发现端口分别是5000和5001

浏览器能够正常向服务器发送请求,服务器也能响应,但是浏览器此时发现端口不一样,即跨域了,数据便不会渲染到页面

解决跨域:

1、cors:后端人员在服务器内返回响应时多加几个特殊的响应头即可,此时从8080向5000请求数据也可以实现,浏览器识别到特殊响应头仍然正常。cors毕竟会导致服务端的安全性差,即什么人都可以从后端跨域申请数据,所以说不常用

2、jsonp解决跨域:借助<script>标签的src属性在引入外部资源时不受同源策略影响的方式实现,前后端代码都需要操作。比上面更少,忽略

3、代理服务器:在8080(本机浏览器)和5000(服务器)两个服务器之间有一个代理服务器(端口也是8080),注意是服务器(服务器之间传数据直接http请求,不考虑跨域)。所以说浏览器向代理服务器发送请求,代理服务器向服务器发送请求,响应也是直接服务器到代理服务器到浏览器(浏览器和代理服务器都是8080,没有跨域问题)。

 开启代理服务器方法:

1、nginx:涉及后端,且内容有点多

2、vue-li:vue脚手架三句代码即可,在vue.config.js内修改vue脚手架

 配置参考 | Vue CLI

//把这代码粘贴到vue.config.js即可
//注意端口是需要访问的服务器端口,代理服务器端口8080脚手架自动帮我们实现
devServer:{
    proxy:'http://localhost:5000'
}

 注意先ctrl+c关掉当前项目再打开,就正常访问到服务器5000端口的students数据

代理服务器缺点:

1、不能灵活控制它是否走代理,也可能在8080代理服务器中直接获取数据。向8080代理服务器请求资源时,如果8080本身就有,便不会请求5000服务器,代理服务器直接就响应了,public的所有资源都看作8080代理服务器本身就有的。虽然有时方便,但是上面的案例中也可能出现public里面就有students的文件,就会出现错误。

2、只能配置一个代理服务器代理服务器只能向一个服务器发送请求信息,

 下面必然未达到要求

 2、方式二

开启代理服务器的代码和上面方式一略有不同

1、/atguigu:请求前缀,在App.vue的请求代理服务器路径当中必须和此处的请求前缀相同,代理服务器就会转发给服务器5000,不相同就不会

2、target:请求路径

3、pathRewrite:App.vue请求时因为会加上前面的请求前缀,但是服务器是没有/atguigu前缀,所以说要去掉,pathRewrite:{'key':'vlaue'},即把key变成value值,下面是把/atguigu变成空

4、写多个请求直接,分隔即可

//开启代理服务器(方式二),改了这里要重启服务npm run serve
devServer: {
  proxy: {
    '/atguigu': {
      target: 'http://localhost:5000',
      pathRewrite:{'^/atguigu':''},
      //ws:true,  //用户支持websocket   默认true 
      //changeOrigin:false  //用于控制请求头中的host值   默认true
    },
    '/demo': {
      target: 'http://localhost:5001',
      pathRewrite:{'^/demo':''},
    }
  }
}

/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/
//开启代理服务器(方式二),vue.config.js当中
//按钮的代码看看即可
<div>
	<button @click="getStudents">获取学生信息</button>
	<button @click="getCars">获取汽车信息</button>
</div>

//App.vue的axios请求代码,注意/atguigu前缀添加位置,其他都不变
methods: {
	getStudents(){
		axios.get('http://localhost:8080/atguigu/students').then(
			response => {
				console.log('请求成功',response.data);
			},
			error => {
				console.log('请求失败',error.message);
			}
		)
	},
	getCars(){
		axios.get('http://localhost:8080/demo/cars').then(
			response => {
				console.log('请求成功',response.data);
			},
			error => {
				console.log('请求失败',error.message);
			}
		)
	}
}

 

 13、vue-resource

发送Ajax请求:

1、xhr

2、jQuery

3、axios

4、fetch

5、vue-resource:vue的插件库,vue1.0时期使用广泛,现在不行了,了解即可。

插件安装:npm i vue-resource 

插件使用方法:Vue.use(xxxx)

//main.js里面配置插件
//引入插件
import vueResource from 'vue-resource'

//使用插件
Vue.use(vueResource)

 不难发现,vue-resource案例中输出的this也有get、post这些。

vue-resource的使用方法和axios基本一致,下面以Ajax的用户搜索案例为例

先删除axios的引入,然后在请求数据代码中把axios变成this.$http即可

 14、slot插 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式

1、不使用插槽

实现如下界面,每部分是一个Category组件

//App.vue代码
<template>
	<div class="container">
        //使用<Category/>然后此时也向其传递数据,因为数据较多,所以说统一:listData
		<Category title="美食" :listData="foods"/>
		<Category title="游戏" :listData="games"/>
		<Category title="电影" :listData="films"/>
	</div>
</template>

<script>
	import Category from './components/Category'
	export default {
		name: 'App',
		components:{Category},
		data(){
            return{
                foods:['火锅','烧烤','小龙虾','牛排'],
                games:['英雄联盟','原神','红色警戒','金铲铲'],
                films:['《教父》','《红辣椒》','《落水狗》','《千年女优》']
            }
        }
	}
</script>
//Category组件,主要看下<li>标签内容就可以了
<template>
    <div class="category">
        <h3>{{ title }}分类</h3>
        <ul>
            <li v-for="(item,index) in listData" :key="index">{{ item }}</li>
        </ul>
    </div>
</template>

<script>
    export default {
        name:'Category',
        props:['listData','title']
        
    }
</script>
2、默认插槽

基本原理:在Category组件内使用<slot>标签

在App.vue使用组件时由<Category>变成<Category>    内容   <Category/>

就可以实现同一个组件生成的三个分类其内容不相同

1、因为没有传递出title以外的数据到Category组件内,且只需要循环遍历games的值即可
2、所以说游戏部分循环代码有更改,Category内propos接收只有title

3、style中的video和img写在两个组件内都可以,区别是先加载完样式再传到category还是传过去再加载样式的区别

4、Ctaegory内只需要一个<slot></slot>便可以接收App填充所有的东西

//App组件
<template>
	<div class="container">
		<Category title="美食" :listData="foods">
			<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
		</Category>
        
		<Category title="游戏" >
			<ul>
				<li v-for="(g,index) in games" :key="index">{{g}}</li>
			</ul>
		</Category>
		
		<Category title="电影">
			<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
		</Category>
	</div>
</template>

<script>
	import Category from './components/Category'
	export default {
		name: 'App',
		components:{Category},
		data(){
            return{
                foods:['火锅','烧烤','小龙虾','牛排'],
                games:['英雄联盟','原神','红色警戒','金铲铲'],
                films:['《教父》','《红辣椒》','《落水狗》','《千年女优》']
            }
        }
	}
</script>

//加上scoped,表示该style只控制App.vue内控件样式,表明Category内的图片视频都是先解析完成再传入Category的slot插槽中的
//总之就是两个地方都可以写视频和图片的样式
<style scoped>
	.container{
		display: flex;
		justify-content: space-around;
	}
	video{
		width: 100%;
	}
	img{
		width: 100%;
	}
</style>

//Category组件
<template>
    <div class="category">
        <h3>{{ title }}分类</h3>

        <!-- 定义一个插槽,即挖个坑,等着组件使用者进行填充 -->
        <slot>我是一个默认值,当使用者没有传递具体结构时,我会出现</slot>     
    </div>
</template>

<script>
    export default {
        name:'Category',
        props:['title']        
    }
</script>
3、具名插槽

特别注意,Vue3废弃了slot和slot-scope,只不过仍然可以用

Vue中的v-slot详解,作用域插槽和具名插槽_屈小康的博客-CSDN博客

1、当定义多个插槽时,每个插槽都必须有自己的名字

<slot name="xxx1"></slot>

<slot name="xxx2"></slot>

2、需要明确告诉那一部分放入哪一个插槽,即添加slot属性或者 v-slot(只有template 标签能用)属性

<img slot="xxx1" src="链接内容" alt="">

<template v-slot:xxx2>   div内容  </template>

在下面加一个链接,可以在一个插槽内实现,也可以单独写一个插槽放置链接

1、同一个插槽内实现:

2、单独写一个插槽实现:

//App组件
<template>
	<div class="container">
		<Category title="美食" :listData="foods">
			<img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
			<a slot="footer" href="http://www.bilibili.com">更多美食</a>
		</Category>

		<Category title="游戏" >
			<ul>
				<li v-for="(g,index) in games" :key="index">{{g}}</li>
			</ul>
			<div class="foot" slot="footer">
				<a href="http://www.atguigu.com">单机游戏</a>
				<a href="http://www.atguigu.com">网络游戏</a>
			</div>
		</Category>

		<Category title="电影">
			<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
			<template v-slot:footer>
				<div class="foot">
					<a href="http://www.atguigu.com">经典</a>
					<a href="http://www.atguigu.com">热门</a>
					<a href="http://www.atguigu.com">推荐</a>
				</div>
				<h4>欢迎前来观影</h4>
			</template>
		</Category>
	</div>
</template>
//Category组件
<template>
    <div class="category">
        <h3>{{ title }}分类</h3>
        <!-- 定义一个插槽,即挖个坑,等着组件使用者进行填充 -->
		<slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot>
		<slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot>        
    </div>
</template>

<script>
    export default {
        name:'Category',
        props:['title']
        
    }
</script>
4、作用域插槽

现在:App组件使用Category,并且数据data也在App内

改为:数据在Category组件的自身,但根据数据生成的结构需要组件的使用者App来决定

以游戏列表为例,一个游戏data生成三种不同结构

//App.vue内
<template>
	<div class="container">

		<Category title="游戏">
			<template scope="atguigu">
				<ul>
					<li v-for="(g,index) in atguigu.games" :key="index">{{g}}</li>
				</ul>
			</template>
		</Category>

		<Category title="游戏">
			<template scope="{games}">
				<ol>
					<li style="color:red" v-for="(g,index) in games" :key="index">{{g}}</li>
				</ol>
			</template>
		</Category>

		<Category title="游戏">
			<template slot-scope="{games}">
				<h4 v-for="(g,index) in games" :key="index">{{g}}</h4>
			</template>
		</Category>

	</div>
</template>
//Category.vue内
<template>
	<div class="category">
		<h3>{{title}}分类</h3>
		<slot :games="games" msg="hello">我是默认的一些内容</slot>
	</div>
</template>

<script>
	export default {
		name:'Category',
		props:['title'],
		data() {
			return {
				games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
			}
		},
	}
</script>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值