非父子通信
Provide和Inject注入
provide:提供;inject:注入
应用场景:
当有上图中这么多组件,加入要app传给banner,或者banner传给list,需要怎么办呢?
1-通过父子通信一个一个传递,可行但是非常麻烦,而且很难维护管理,因为这个组件树可能很深。
2-专门的状态管理库,如vuex/pinna,这个后面几天会学
3-就是现在所学的,provide/inject
4-事件总线
了解provide/inject(实际开发用的较少,一般都是通过vuex和pinna)
基本使用就是在父亲组件(只要在使用者的上层即可)写入provide,在使用者也就是子组件中写入inject即可。
缺点:数据是写死的,实际上一般数据是从服务器到达data内,然后再到达provide
所以我们需要函数写法,让provide能取到data中的数据
如果是对象写法,this是取不到data的数据的,函数写法可以因为vue底层和给methods一样给provide函数做了this指向修改绑定。
这个响应式数据的意思就是,当父级的provide给子级的数据发生变化,子级里面使用到这个数据的地方会不会自动改变。(你直接修改代码发现变了只是因为浏览器刷新了一遍,你需要在不刷新的情况下通过事件变化数据来验证)
1-为什么computed用箭头函数,因为只有箭头函数沿用上一级的this,才能取到names
2-如上如果让子组件中接收数据的length(这个length已经是开辟了新的空间)发生变化,父组件provide中的数据是不变化的,因为改的不是同一个变量。
事件总线eventbus
bus在编程里面是总线的意思
为什么需要事件总线?
因为provide和inject只能实现有祖先层级关系的组件中通信,属于最上图的app传给banner,如果是banner传给list,就需要事件总线(当然状态管理库vuex/pinna也可以)
事件总线在最基本的父子通信中也可以使用,看起来写法似乎更简单,但是不推荐,因为不利于维护管理,一般只有跨层级较多间的组件通信才使用。
coderwhy老师在高级中教了自己怎么封装了事件总线的库,我看的是pink老师的高级,所以没学。
在这里我只学习hy-event-store库中事件总线的使用。
步骤:
1-npm install hy-event-store 安装这个库(王红元coderwhy老师自己写的库)
我很少用new这种东西,所以不太熟悉类
类是对象的抽象,而对象是类的具体实例
JavaScript类就是一个可以创建很多相似对象的函数,也就是我们学的构造函数
Object()函数就是JavaScript自带的构造函数,也就是JavaScript自带的一个“类”
用“类”创建的对象就是“实例”
如果一个创建了2个对象,那么他就有两个实例
2-为什么有的导入import后面加大括号,有的不加?
https://blog.csdn.net/qq_37388085/article/details/121860660这个东西在框架前置课学过,我忘记了。
默认导出的就可以不加括号,一个文件中只能有一个默认导出。
3-创建utils文件夹,创建一个js文件来存储需要从当前库导出的类。
import { HYEventBus } from 'hy-event-store'
const eventBus = new HYEventBus()
export default eventBus
3.1导入类HYEventBus,这个名字是固定的,就相对于 new object的object,是王红元老师自己写的一个类
3.2然后通过这个类创建一个eventBus对象,这个对象名字就是我们可以随便写的,自己记住是什么就好了
3.3导出eventBus这个事件总线对象,供其他组件使用
4.1其他组件使用eventBus这个类,发出者通过emit方法
eventBus.emit("whyEvent", "why", 18, 1.88)
//通过emit方法发出通信,第一个为自定义事件名字,后面的是变量
//注意你这个语句得写在export default{}里面,那么其他组件才能接收到,你直接写在script里,但是没有导出,那么其他组件是接收不到的,只在当前页面运行了一遍语句。
4.2事件监听者通过on方法
created() {
// fetch()
// 事件监听
eventBus.on("whyEvent", (name, age, height) => {
console.log("whyEvent事件在app中监听", name, age, height)
this.message = `name:${name}, age:${age}, height:${height}`
})
}
created和unmounted是生命周期,后面会学到,这里知道监听写在这个里面即可。
事件总线事件的取消
应用场景:当我们给一个组件添加了事件总线的事件监听,但是当这个组件有时候需要被销毁也就是不需要这个组件显示了的时候,这个监听事件当然也要被销毁移除掉最好。
export default {
methods: {
whyEventHandler() {
console.log("whyEvent在category中监听")
}
},
created() {
eventBus.on("whyEvent", this.whyEventHandler)
},
unmounted() {
console.log("category unmounted")
eventBus.off("whyEvent", this.whyEventHandler)
}
}
this.whyEventHandler就是事件监听后会执行的函数,在eventBus.off销毁的时候,需要指明销毁函数。
(这个东西是王红元老师封装好的库,和mitt(很久没维护了)这些常用的事件总线库是一样的,我们只需要知道怎么用,它要求的语法是怎么写,我们就怎么写记住就好)
组件化知识补充
最重要的也是必须会的是组件的生命周期和组件中的ref引用,其他的了解即可。
tips:相对路径符号
/代表整个电脑或者服务器的根目录
./代表当前文件相对路径下
../为上一层
@为src文件下(vue官方设置的别名)
那么上上层如何取到呢?
答: ../../即可,上上上层就是../../../
生命周期
重要,必须理解,不然你根本无法判断什么时候在做什么。
$refs的使用
refs:References引用的意思
应用场景:当我们在一些特殊情况,需要拿到元素dom对象或者子组件实例
注意:因为vue是声明式编程框架,我们千万不要在vue中去出现原生的js的dom操作语句,比如queryselector什么的,虽然也是可编译执行的,但是那就违背了框架的初衷。
那么实在要拿到这个dom对象或者子组件实例怎么办呢?又不能写原生dom语句。
答:使用vue框架提供的ref属性
<template>
<div class="app">
<h2 ref="title" class="title" :style="{ color: titleColor }">{{ message }}</h2>
<button ref="btn" @click="changeTitle">修改title</button>
<banner ref="banner"/>
</div>
</template>
<script>
import Banner from "./Banner.vue"
export default {
components: {
Banner
},
data() {
return {
message: "Hello World",
titleColor: "red"
}
},
methods: {
changeTitle() {
// 1.不要主动的去获取DOM, 并且修改DOM内容
// this.message = "你好啊, 李银河!"
// this.titleColor = "blue"
// 2.获取h2/button元素
console.log(this.$refs.title)
console.log(this.$refs.btn)
// 3.获取banner组件: 组件实例
console.log(this.$refs.banner)
// 3.1.在父组件中可以主动的调用子组件的对象方法
this.$refs.banner.bannerClick()
// 3.2.获取banner组件实例, 获取banner中的根元素(也就是最外层的div)
console.log(this.$refs.banner.$el)
// 3.3.如果banner template是多个根, 拿到的是第一个node节点
// 注意: 开发中不推荐一个组件的template中有多个根元素
// console.log(this.$refs.banner.$el.nextElementSibling)
// 4.组件实例还有两个属性(了解):
console.log(this.$parent) // 获取父组件
console.log(this.$root) // 获取根组件
}
}
}
</script>
<style scoped>
</style>
1-给需要获取的元素或者组件加上属性ref
2-vue会把所有有ref属性的dom放到一个$refs的对象里面
3-获取的如果是组件,那么就是一个代理实例对象
4-所以我们可以通过这个实例对象去调用子组件拥有的方法
$parent和$root了解即可
动态组件
需求:点击tab栏实现组件显示的切换
<template>
<div class="app">
<div class="tabs">
<template v-for="(item, index) in tabs" :key="item">
<button :class="{ active: currentTab === item }"
@click="itemClick(item)">
{{ item }}
</button>
</template>
</div>
<div class="view">
<!-- 1.第一种做法: v-if进行判断逻辑, 决定要显示哪一个组件 -->
<!-- <template v-if="currentIndex === 0">
<home></home>
</template>
<template v-else-if="currentIndex === 1">
<about></about>
</template>
<template v-else-if="currentIndex === 2">
<category></category>
</template> -->
<!-- 2.第二种做法: 动态组件 component -->
<!-- is中的组件需要来自两个地方: 1.全局注册的组件 2.局部注册的组件 -->
<!-- <component :is="tabs[currentIndex]"></component> -->
<component name="why"
:age="18"
@homeClick="homeClick"
:is="currentTab">
</component>
</div>
</div>
</template>
<script>
import Home from './views/Home.vue'
import About from './views/About.vue'
import Category from './views/Category.vue'
export default {
components: {
Home,
About,
Category
},
data() {
return {
tabs: ["home", "about", "category"],
// currentIndex: 0
currentTab: "home"
}
},
methods: {
itemClick(tab) {
this.currentTab = tab
},
homeClick(payload) {
console.log("homeClick:", payload)
}
}
}
</script>
<style scoped>
.active {
color: red;
}
</style>
关键就是直接写component标签,里面有一个is属性,如下等于哪个组件名字就显示哪一个组件
<component name="why"
:age="18"
@homeClick="homeClick"
:is="currentTab">
</component>
其中组件中父传子传值和通信和原先是一样的,如上代码的传值与自定义事件监听。
认识Keep-alive
目的:提高性能,和保存数据
为什么?
1- 提高性能:如上一个tab动态组件切换案例,当home组件切换为about组件,home组件其实会经历unmounted被销毁了,当重新切换为home又会重新创建,如果是需要频繁切换,无疑会增加性能损耗。
所以我们可以让某一个组件在没被使用和显示时也保持存活。
2-保存数据:如about组件中在使用的时候data中用户使用有一些数据进行了变动,但是切换到about再切换为about,那么这些数据将会消失回到默认状态而不是用户使用后的状态。
用法
<template>
<div class="app">
<div class="tabs">
<template v-for="(item, index) in tabs" :key="item">
<button :class="{ active: currentTab === item }"
@click="itemClick(item)">
{{ item }}
</button>
</template>
</div>
<div class="view">
<!-- include: 组件的名称来自于组件定义时name选项 -->
<keep-alive include="home,about">
<component :is="currentTab"></component>
</keep-alive>
</div>
</div>
</template>
在需要保持存活的组件标签外加上keep-alive标签即可
问题:在动态组件里,我想有的存活有的不存活怎么办呢?
使用如上代码的include属性,只有在include内有名字的组件才保持存活。注意这里include识别的不是当前组件导入的组件名称,而是以需要存活的子组件内的name为准,如下写法。
name:’home’
<script>
export default {
name: "home",
data() {
return {
counter: 0
}
},
created() {
console.log("home created")
},
unmounted() {
console.log("home unmounted")
},
// 对于保持keep-alive组件, 监听有没有进行切换
// keep-alive组件进入活跃状态
activated() {
console.log("home activated")
},
//不活跃状态
deactivated() {
console.log("home deactivated")
}
}
</script>
keep-alive的一些属性
include就是需要存活的,exclude就是里面写了的不存活其他的存活。
需求补充:
当我们保持存活后,home到about或者其他组件等动作就无法提供生命周期的created和unmounted函数监听到了,那么怎么办呢?
vue专门提供了另外两个方法
// keep-alive组件进入活跃状态
activated() {
console.log("home activated")
},
//不活跃状态
deactivated() {
console.log("home deactivated")
}
Webpack代码分包
一般npm run build即npm vue-cli-service bulid会通过webpack给代码打包,然后生成dist文件夹,然后我们再把dist部署到服务器供用户使用。
map文件是一种映射文件,给我们线上调试告诉我们哪一行代码出错的,现在先忽略,后续深入工程讲webpack高级会学习。
为什么学习这个东西?为了首屏渲染速度
如上,chunk就是组块分包的意思,vendors就是供应商,所以chunk.vendors就是保存了我们第三方库的js如vue.js和原先使用的第三方事件总线库hy-event-store.js; app.xxxx就是我们自己写的js
为什么有首屏渲染速度的问题?
cw老师说后续ssr渲染课程也会学到(我先学完vue3找个实习后面再学这个),这里大概意思就是:
像上面的动态组件案例,当我们写了home,about,category很多组件,用户可以通过tab栏进行切换,但是用户在第一次加载的时候,只加载home页面,那么因为webpack把所有代码打包为了app.xx.js,用户就需要下载很多home页用不到的js代码,影响了加载速度。 所以我们需要分包。
如何分包?
工厂函数:专门生成某种对象的函数。
在导入的时候通过import函数导入即可,它可以异步导入还可以让webpack进行分包处理(这个函数返回的是一个promise对象)
那么在普通的组件内是如何进行分包的呢?
工厂写法:
import { defineAsyncComponent } from 'vue'
import Home from './views/Home.vue'
import About from './views/About.vue'
// import Category from './views/Category.vue'
// const Category = import("./views/Category.vue")
const AsyncCategory = defineAsyncComponent(() => import("./views/Category.vue"))
1-vue为我们提供了一个defineAsyncComponent 定义异步组件的方法,我们先导入
2-通过这个方法声明异步组件(import函数导入返回一个promise对象)
3-注册组件
对象写法(少见,了解即可)
v-model的本质
app.vue
<template>
<div class="app">
<!-- 1.input v-model 普通应用本质其实就是监听事件然后赋值-->
<!-- <input v-model="message">
<input :value="message" @input="message = $event.target.value"> -->
<!-- 2.组件的v-model: 默认modelValue -->
<counter v-model="appCounter"></counter>
<counter :modelValue="appCounter" @update:modelValue="appCounter = $event"></counter>
<!-- 3.组件的v-model: 自定义名称counter -->
<!-- <counter2 v-model:counter="appCounter" v-model:why="appWhy"></counter2> -->
</div>
</template>
<script>
import Counter from './Counter.vue'
import Counter2 from './Counter2.vue'
export default {
components: {
Counter,
Counter2
},
data() {
return {
message: "Hello World",
appCounter: 100,
appWhy: "coderwhy"
}
}
}
</script>
<style scoped>
</style>
Counter.vue
<template>
<div>
<h2>Counter: {{ modelValue }}</h2>
<button @click="changeCounter">修改counter</button>
</div>
</template>
<script>
export default {
props: {
modelValue: {
type: Number,
default: 0
}
},
emits: ["update:modelValue"],
methods: {
changeCounter() {
this.$emit("update:modelValue", 999)
}
}
}
</script>
<style scoped>
</style>
counter2.vue
<template>
<div>
<h2>Counter: {{ counter }}</h2>
<button @click="changeCounter">修改counter</button>
<!-- why绑定 -->
<hr>
<h2>why: {{ why }}</h2>
<button @click="changeWhy">修改why的值</button>
</div>
</template>
<script>
export default {
props: {
counter: {
type: Number,
default: 0
},
why: {
type: String,
default: ""
}
},
emits: ["update:counter", "update:why"],
methods: {
changeCounter() {
this.$emit("update:counter", 999)
},
changeWhy() {
this.$emit("update:why", "kobe")
}
}
}
</script>
<style scoped>
</style>
认识Mixin(vue2常用,vue3已经不用)
应用场景: 如很多个组件有相同的optionsapi,那么就可以把相同的代码抽取到一个mixins.js文件内,然后复用即可。
如上,通过mixins:[xxxxx],就导入了mixin里面的js,但是当混入的js中有data()等,自己组件又写了data()等会怎么样呢?
它们会合并,如果有命名冲突,则保留组件内的
全局混入:所有组件都需要使用混入的js