框架技术Vue ---- watch监听、组件生命周期和数据共享、全局注册属性_vue生命周期watch

+ - [监听组件的不同时刻created mounted unmounted](#created_mounted_unmounted_224)
	- [监听组件的更新updated](#updated_288)
	- [组件主要生命周期函数 应用](#__313)
	- [组件中所有的生命周期函数](#_322)
+ [组件间数据共享](#_333)
+ - [父子组件之间的数据共享](#_345)
	- [兄弟组件之间的数据共享 EventBus](#_EventBus_354)
	- [后代关系组件之间的数据共享](#_448)
	- * [父结点使用provide共享数据](#provide_452)
	- [后代组件使用inject结点接收数据](#inject_483)
	- [基于provide共享响应式数据【按需导入computed函数】](#providecomputed_512)
	- [vuex 大范围的数据共享](#vuex__542)
+ [vue3.x全局配置axios](#vue3xaxios_551)
+ - [main.js通过app.config.globalProperties全局挂载](#mainjsappconfigglobalProperties_558)
+ [组件高级案例 --- 购物车](#___593)

Vue3基础:组件化开发高级 ----- watch监听器,vue的生命周期,数据共享,配置axios


前面简单介绍了组件基础,包括计算属性,动态绑定和props传值和自定义事件等,这个过程中,最复杂的css样式是直接使用的bootstrap进行渲染

watch侦听器

watch侦听器允许开发者监控数据的变化【区别于Servlet Listener】,从而针对数据变化执行特定的操作,比如监视用户名的变化并发起请求,判断用户名是否可用

基本使用 watch结点

要使用自定义的侦听器,需要在watch结点下面进行定义,watch结点和data,name,methods,computed,emits,components等结点平级 /形参列表中,第一个值是变化后的新值,第二个是变化之前的旧值 其实类似一个函数 + 事件;和计算属性类似,只要监听的值发生变化,自动调用该函数中的表达式

export default {
	data() {
        return {
            username: ''
        }
    },
    watch: {
        //监听username的值的变化
        //形参列表中,第一个值是变化后的新值,第二个是变化之前的旧值
        //watch可以直接调用data中的数据项,不需要使用this调用
        username(newVal,oldVal) {
          console.log(newVal,oldVal)  //相当于也是一个函数,变化的时候对前后的值进行操作 
        },
    },
}

这里最简单的用法就是直接将监控的数据项作为函数的名称,接收的参数就是变化后和前的值

<template>
  <img alt="Vue logo" src="./assets/logo.png" /><br/>
  姓名<input type="text" v-model.lazy="username"/>
</template>

<script>

export default {
  name: 'App',
  components: {
	
  },
  data() {
	  return {
		  username: '',
	  }
  },
  watch: {
	  //监听username数据的变化,相当于一个事件自动触发,和computed类似
	  username(newVal,oldVal) {
		  console.log(newVal,oldVal)
	  }
  }
}
</script>

使用watch检测用户名是否可用

监听username值的变化,并使用axios发起ajax请求,检测当前输入的用户名是否可用

  • 首先就是安装依赖包axios npm i axios -S — 安装到运行依赖中
  • 之后就是导入依赖包,使用async和await来简化发送ajax的Promise的异步返回值
<script>
import axios from 'axios'

export default {
  name: 'App',
  components: {
	
  },
  data() {
	  return {
		  username: '',
	  }
  },
  watch: {
	  //这里使用了async和await来简化了Promise异步操作 --- 得到的就是具体的数据了,而不是Promise对象
	  async username(newVal,oldVal) {
			const {data: res} = await axios.get('https://www.escook.cn/api/finduser/' + newVal)
			console.log(res)
	  }
  }
}
</script>

https://www.escook.cn/api/finduser/ 这是一个部署了的web应用【功能就是可以查询用户名是否重复】 — 方便校验前端的功能

{status: 0, message: '用户名可用!'}
message: "用户名可用!"
status: 0
[[Prototype]]: Object

根据控制台打印的数据,返回的数据对象中,只有data是需要的,所以这里直接通过解构的方式来获取,因为axios.get(‘https://www.escook.cn/api/finduser/’ + newVal)就是指代的这个对象【之前已经用过多次,const{data:res} ---- 解构出这个对象的data属性,并重命名为res

immedidate选项---- watch的数据项变为对象

默认情况下,组件在初次加载完毕之后不会调用watch侦听器,如果想要watch侦听器立即使用,需要使用immediate选项、【比如上面的username查重,如果初始值不是’'空,而是具体的值,那么默认是不会检查这个初始数据

使用这个选项,那么watch中的监听的数据项就不是一个简单的方法了,而是一个对象,之前的操作方法名使用handler属性替代: 当数据项发生变化时,调用handler

  watch: {
	  //handler属性可以代替之前的简单的函数写法,其中的参数和之前相同
	  username: {
		  async handler(newVal,oldVal) {
		  		const {data: res} = await axios.get('https://www.escook.cn/api/finduser/' + newVal)
		  		console.log(res)
		  },
		  //表示组件加载完毕后立即监听该数据项
		  immediate: true
	  }
	  
  }

这里一开始就会对上面的的初始的username进行验证,一开始就被触发

deep配置项

使用watch进行侦听对象的值的变化的时候,如果对象的属性值发生了变化,就无法被监听,这个时候就要使用deep选项

也就是说: 这里监听的值不再时直接的一个值,而是一个对象的其中一个属性,【按照之前的写法:这里就只能写对象的名称,而不是直接时属性】

  姓名<input type="text" v-model.lazy="info.username"/>

这里在watch进行侦听

	  info: {
		  async handler(newVal) {
		  		const {data: res} = await axios.get('https://www.escook.cn/api/finduser/' + newVal.username)
		  		console.log(res)
		  },
		  //表示组件加载完毕后立即监听该数据项
		  immediate: true
	  }

这里通过.引用的方式,并没有监听到info的username属性值的变化

那么要想能够监听到,就要加上deep的选项,也是Boolean类型

  watch: {
	  //这里因为时直接写data中的数据项,所以这里时info,不能写info.username
	  info: {
		  async handler(newVal) {
		  		const {data: res} = await axios.get('https://www.escook.cn/api/finduser/' + newVal)
		  		console.log(res)
		  },
		  //表示组件加载完毕后立即监听该数据项
		  immediate: true,
		  deep:true
	  }

这样,就可以监听到对象info的属性值的变化了

监控单个属性的变化;直接’obj.pro’

上面的deep虽然支持了监听对象的属性值的变化,但是问题是,会监听其所有的属性的变化,只要某个属性变化,就会调用handler函数

  data() {
	  return {
		  info: {
			  username: '张三',
			  age:21,
		  }
	  }
  },

比如这里如果修改age属性的值,handler函数也会触发,然后返回’用户名被占用’,显然不符合预期

如果只是想要监听对象的单个属性的变化,直接通过访问链的方式和最初的方式来定义即可

'info.username' : {
	async handler(newVal){
		.....
	},
	immediate:true
}

这样就可以 监控info的username属性的变化; 变化age属性的值,就不会再触发handler函数【这个时候返回的值就不是对象,而是一个字符串了】

计算属性和watch侦听器

这两者都有相似的地方:就是和data的数据项关联,不同点:

侧重的应用场景不同: 计算数学侧重监听多个值的变化【只要再computed中的函数中使用到的data的数据项值发生变化,都会自动进行计算并缓存直到再次改变】,最终返回的是一个新值, 侦听器侧重监听单个数据的变化,最终执行的特定的业务逻辑,不需要任何的返回值 — 所以最表面的区别就是返回值

组件生命周期

下面的这张图,vue3销毁使用的是unmounted,不再是destroy【其余还是一样的】,两个生命周期函数就是beforeUnmount和unmounted

在这里插入图片描述

组件的运行: 首先import导入组件----------- > 通过components结点注册私有组件,或者在mian.js中使用component方法注册全局组件,---------> 之后以标签调用的方式使用组件- -----> 在内存中创建组件的实例对象 ----> 将创建的组件实例渲染到页面上 ----- > 组件切换时销毁需要被隐藏的组件【之前vue2就是渲染的对象new Vue根组件】

组件的生命周期指的是: 组件从创建-> 运行(渲染) -> 销毁的整个过程,这是一个时间段,之前的servlet也分享过生命周期

监听组件的不同时刻created mounted unmounted

vue框架为组件内置了不同时刻的生命周期函数,生命周期函数会伴随着组件的运行而自动调用:

  • 当组件在内存中被创建完毕之后,会自动调用create函数
  • 当组件被成功渲染到页面的时候,会自动调用mounted函数
  • 当组件被销毁完毕之后,会自动调用unmounted函数

组件的销毁对应的就是隐藏组件对应的标签,可以使用v-if标签隐藏销毁

这里在根组件App中引入子组件life-circle

  <life-circle v-if="flag"></life-circle>

内置的这3个函数可以直接在组件的脚本区域调用【和data、name等平级】

<template>
	<div>
		LifeCircle子组件
		使用者<input type="text" v-model.trim="user" />
	</div>
</template>

<script>
	export default {
		//组件被创建之后自动调用的内置函数
		created() {
			console.log('组件被创建' + new Date())
		},
		//组件被渲染到页面后自动调用mounted函数
		mounted() {
			console.log('组件被渲染运行' + new Date())
		},
		//组件被销毁之后自动调用unmounted函数
		unmounted() {
			console.log('组件被销毁' + new Date())
		},
		data() {
			return {
				user: 'Cfeng'
			}
		}
	}
</script>

<style lang="less" scoped>
</style>

这里在子组件和data平级的位置就显化了这3个内置的函数,销毁对应的就是父组件将标签隐藏

组件被创建Tue Mar 15 2022 17:18:58 GMT+0800 (中国标准时间)
组件被渲染运行Tue Mar 15 2022 17:18:58 GMT+0800 (中国标准时间)
组件被销毁Tue Mar 15 2022 17:19:23 GMT+0800 (中国标准时间)

这里组件销毁是因为将父组件的flag值改为了false

当再次将flag改为true的时候,会重新创建这个子组件的实例

监听组件的更新updated

当组件的data数据更新之后,vue会自动重新渲染组件的DOM结构,从而保证View视图展示的数据和Model的数据源保持一致, 当组件被重新渲染完毕后,会自动调用生命周期函数updated

		//组件被创建之后自动调用的内置函数
		created() {
			console.log('组件被创建' + new Date())
		},
		//组件被渲染到页面后自动调用mounted函数
		mounted() {
			console.log('组件被渲染运行' + new Date())
		},
		//组件的data数据被更新之后会自动调用updated函数
		updated() {
			console.log('组件更新重新渲染' + new Date())
		}
		//组件被销毁之后自动调用unmounted函数
		unmounted() {
			console.log('组件被销毁' + new Date())
		},

这里只要修改了子组件life-circle的user,那么就会执行该函数

组件主要生命周期函数 应用

在上面介绍的4个生命周期函数中,created、mounted、unmounted都是只执行唯一依次,但是updated会执行0或者多次;

  • created : 发送ajax请求接收初始的数据
  • mounted: 操作DOM元素 ---- 渲染到界面

另外的两个就可以根据具体情况来进行操作

组件中所有的生命周期函数

vue中内置的生命周期函数一共8个,就是在之前的4个函数的基础上,加上before即可,因为上面的4个函数为—之后,加上before就是 —之前

  • beforeCreate 在内存开始创建组件之前 【唯一一次】
  • beforeMount 在把组件初次渲染到页面之前 【唯一一次】
  • beforeUpdate 在组件重新渲染之前 【0或多次】
  • beforeUnmount 在组件被销毁之前 【唯一一次】

注意: beforeCreate时候组件还没有被创建,不能发送ajax请求【最早要在Created发送】,并且在beforeMount中也不能操作DOM元素,因为还没有被渲染到页面中【最早要Mounted中操作】

组件间数据共享

在项目开发中,组件之间的关系主要有3种:

  • 父子关系
  • 兄弟关系
  • 后代关系

在这里插入图片描述

关于这里的关系和数据结构的树类似,不再赘述

父子组件之间的数据共享

父子组件的数据共享分为

  • 父向子共享数据 — 之前已经在props位置解释过,就是父组件通过v-bind属性绑定向子组件共享数据,同时,子组件需要使用props接收数据
  • 子向父共享数据 ----- 自定义事件的方式向父组件共享数据,这里也是昨天的实例中使用过,通过自定义的事件的参数携带数据
  • 父和子进行双向的数据共享 — 在父组件的标签调用上加上v-model指令,并且在子组件声明自定义事件update: 属性; — 然后就可以将这个属性值和父组件的data的数据进行双向绑定
兄弟组件之间的数据共享 EventBus

兄弟之间实现数据共享的方案是EventBus,可以借助第三方的包mitt来创建eventBus对象,从而实现数据共享

首先就是要安装mitt包,并且使用其中的方法,创建一个bus对象,

数据的接收方,使用bus.on(‘自定义事件’,(data) => {处理逻辑})来接收处理数据【on监听接收】{on在组件的created函数中声明了数据共享的自定义事件}

然后在数据发送方,使用bus.emit(‘自定义事件’,要发送的数据)来发送数据,【emit触发分发送数据,触发自定义事件】

运行npm i mitt -S 在项目中安装依赖包mitt【使用bus对象】

  • 创建一个公共的文件EventBus.js,【功能就是使用mitt包同时默认导出一个bus对象】
import mitt from 'mitt'

//创建一个bus实例对象,不需要new
const bus = mitt();

//使用默认导出将bus导出
export default bus

  • 在数据接收方cosumKid组件,需要在created函数中,使用bus.on方法注册一个自定义事件 ---- 因为数据共享就是在最开始就应该进行,所以就是在组件的实例创建之后就注册一个自定义的事件
<template>
	<div>
		ConsumKid子组件<br>
		X * 2 + 1的结果为 :<span style="background-color: aqua;">{{count * 2 + 1}}</span>
	</div>
</template>

<script>
	import bus from '../EventBus.js'
	
	export default {
		name:'ConsumKid',
		created() {
			bus.on('numChange',(num) => {
				this.count = num
			})
		},
		data() {
			return {
				count: 0
			}
		}
	}
</script>

<style lang="less" scoped>
</style>

  • 在数据的发送方life-circle组件,这里就可以结合侦听器来发送数据,当数据发生变化的时候,触发上面声明的自定义事件,发送数据【 这样就在兄弟组件中实现了同一个组件的计算属性的效果】
<template>
	<div>
		LifeCircle子组件
		发送的数据 --- 原始数字<input type="text" v-model.number="num" />
	</div>
</template>

<script>
	import bus from '../EventBus.js'
	
	export default {
		data() {
			return {
				num: 0
			}
		},
		//在监听器中触发自定义事件发送数据
		watch:{
			num:{
				handler(){
					//使用bus对象的emit方法派发数据
					bus.emit('numChange',this.num)
				},
				immediate:false
			}
		}
	}
</script>

<style lang="less" scoped>
</style>

handler其实就是一个方法,可以接收参数,也可以不接收参数,接收的参数就是newVal和oldVal

后代关系组件之间的数据共享

后代关系组件之间共享数据,指的是父节点的组件向子孙结点共享数据,此时嵌套关系复杂,可以使用provoid和inject(注入)来实现数据共享---- 发送方使用provide,接收方使用inject 【无直接关系的结点不能使用】

父结点使用provide共享数据

provide结点与data,methods结点等平级

export default {
  name: 'App',
  components: {
	LifeCircle,
	CosumKid
  },
  data() {
	  return {
		  info: {
			  username: '张三',
			  age:21,
		  },
		  flag: true,  //控制标签的显示
		  color: 'pink',  //传递给后代的数据
	  }
  },
  provide() {//provide和data一样为函数,返回值就是要共享的数据,闭包
	  return {
		  color: this.color,
	  }
  }
}

直接通过provide结点共享了数据color — color值可以任意赋值;和data类似

后代组件使用inject结点接收数据

后代结点,包括父节点的子结点和其下的子组件…,这里就简单使用子组件来演示,在子组件中,定义inject结点,和data平级,接收数据,直接通过数组的形式,接收得到的数据和data中的数据一样可以放到DOM中

export default {
		name:'ConsumKid',
		created() {
			bus.on('numChange',(num) => {
				this.count = num
			})
		},
		data() {
			return {
				count: 0,
			}
		},
		inject:['color']
	}


-------------在上面的template中------------
X * 2 + 1的结果为 :<span :style="{'background-color':color}">{{count * 2 + 1}}</span>

这里的color就是进行了属性赋值

这样后代的组件直接就可以使用祖先结点的数据,而不必是data中的数据

基于provide共享响应式数据【按需导入computed函数】

上面的provide有个问题,就是数据是静态的,不是响应式的,也就是父节点的共享数据发生了变化,子节点的数据并没有发生变化【也就是不会更新】,那么如何共享响应式的数据呢?

computed是计算属性,同时,在vue中,提供了computed函数可以帮助共享响应式的数据【按需导入即可,和之前的createApp类似】使用computed函数,可以将数据包装为响应式数据

provide(){
    return {
        color: computed(()=>{this.color})
    }
}

就类似于计算属性,computed函数也是当其中的data数据发生变化的时候就会重新计算,但是和计算属性不同,不需要return,直接将return的结果写出即可

import {computed} from 'vue'

provide() {//provide和data一样为函数,返回值就是要共享的数据,闭包
	  return {
		  color: computed(() => {this.color}),
	  }
  }

变成响应式数据之后,子组件接收的时候就要加上.value,不然会警告

[Vue warn]: injected property “color” is a ref and will be auto-unwrapped and no longer needs .value in the next minor release. To opt-in to the new behavior now, set app.config.unwrapInjectedRef = true (this config is temporary and will not be needed in the future.

vuex 大范围的数据共享

vuex是终极的组件之间的数据共享方案,在企业级的vue项目开发中,vuex可以让组件之间的数据共享变得更加高效、清晰,并且易于维护

在这里插入图片描述

如果组件间的数据不需要共享,就不需要vuex了,vuex提供了一个中转的数据站STORE,所有共享的数据都由发送方发送给它,并且由它将数据发送给接收方,虽然多了中转站,但是至少是统一管理数据共享,和Spring的AOP全局异常处理类类似

vue3.x全局配置axios

axios就是发送ajax请求的,在实际项目开发中,几乎每个组件都会用到axios发起数据请求【data】,如果不全局配置,那么问题就是:

  • 每一个组件都需要导入axios 【代码臃肿】
  • 每一次发起请i去都要写完整的请求路径【不能相对路径】,不利于后期的维护
main.js通过app.config.globalProperties全局挂载

要全局配置axios,需要在main.js文件中,使用app.config.globalProperties进行全局挂载

可以通过defaults.baseURL指定相对路径,使用pp.config.globalProperties.$http = axios挂载axios

然后组件就可以通过this.$http.get(‘相对路径’) 发起请求 这里的名称是自定义的,可以使用ajax

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import './assets/css/bootstrap.css'

//导入axios
import axios from 'axios'

const spa_app = createApp(App)

//在mount之前进行配置
//声明请求的相对路径
axios.defaults.baseURL = 'https://www.escook.cn/api'

//全局注册挂载
spa_app.config.globalProperties.$ajax = axios

spa_app.mount('#app')

相当于先使用axios的default的baseURL定义相对路径,然后将axios注册为全局的属性,这里在组件中可以使用this进行调用, 注意是defaults,不要少写s

const res = this.$ajax.get('/finduser/'+ newVal)

组件高级案例 — 购物车

这里的案例的效果和之前的水果案例类似,最核心的部分就是中间的商品列表,实现的思路也很简单,因为使用组件化思想,封装几个子组件就可以了

  • 初始化项目基本结构
npm init vite-appp code-cart

cd code-cart

npm i

npm run dev   //初始化了项目

//将bootstrap文件导入,因为css样式不想自己编写,用现成的就好,毕竟我不是专业的前端
整理项目的目的结构

npm i less -D

//初始化全局样式
	:root {
		font-size: 12px
	}

main.js

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import './assests/css/bootstrap.css'

const spa_app = createApp(App)

spa_app.mount('#app')

  • 封装EsHeader组件

封装的要求: 和之前的MyHeader组件相同,允许自定义title属性,自定义color文字颜色,bgcolor背景颜色,fsize字体大小,固定定位,高度45px,文本居中,z-index为999

<template>
	<div class="header-container" :style="{'background-color':bgcolor,'color':color,'font-size':fsize}">
		{{title}}
	</div>
</template>

<script>
	export default {
		name:'EsHeader',
		props: {
			title: {
				type:String,
				default:'es-header',
				required:true
			},
			color: {
				type: String,
				default:'yellow'
			},
			bgcolor: {
				type: String,
				default:'pink'
			},
			fsize: {
				type: Number,
				default: 12,
			},
		}
	}
</script>
### 总结

**前端资料汇总**

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**

![](https://img-blog.csdnimg.cn/img_convert/6e0ba223f65e063db5b1b4b6aa26129a.png)

*   框架原理真的深入某一部分具体的代码和实现方式时,要多注意到细节,不要只能写出一个框架。

*   算法方面很薄弱的,最好多刷一刷,不然影响你的工资和成功率😯



*   在投递简历之前,最好通过各种渠道找到公司内部的人,先提前了解业务,也可以帮助后期优秀 offer 的决策。

*   要勇于说不,对于某些 offer 待遇不满意、业务不喜欢,应该相信自己,不要因为当下没有更好的 offer 而投降,一份工作短则一年长则 N 年,为了幸福生活要慎重选择!!!
    喜欢这篇文章文章的小伙伴们点赞+转发支持,你们的支持是我最大的动力!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值