+ - [监听组件的不同时刻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, setapp.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 年,为了幸福生活要慎重选择!!!
喜欢这篇文章文章的小伙伴们点赞+转发支持,你们的支持是我最大的动力!