文章目录
Vue 基础知识(2.x)
生命周期
vue生命周期分为四个阶段和八个生命周期钩子函数
阶段 | 钩子函数 |
---|---|
创建阶段 | beforeCreate,created |
挂载阶段 | beforeMount,mounted |
更新阶段 | beforeUpdate,updated |
销毁阶段 | beforeDestroy,destroyed |
-
beforeCreate
在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用。在这个阶段,数据是获取不到的,并且真实 dom 元素也是没有渲染出来的
-
created
在实例创建完成后被立即调用。
在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。然而,挂载阶段还没开始,且 $el、property 目前尚不可用。在这个阶段,可以访问到数据了,但是页面当中真实dom节点还是没有渲染出来,在这个钩子函数里面,可以进行相关初始化事件的绑定、发送请求操作
-
beforeMount
在挂载开始之前被调用。
相关的 render 函数首次被调用。代表 dom 马上就要被渲染出来了,但是却还没有真正的渲染出来,这个钩子函数与 created 钩子函数用法基本一致,可以进行相关初始化事件的绑定、发送请求操作
-
mounted
实例被挂载后调用。
这时 el 被新创建的vm.$el
替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时vm.$el
也在文档内。挂载阶段的最后一个钩子函数,数据挂载完毕,真实 dom 元素也已经渲染完成了,这个钩子函数内部可以做一些实例化相关的操作
-
beforeUpdate
在数据发生改变后,DOM 被更新之前被调用。
这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。这个钩子函数初始化时不会执行,当组件挂载完毕的时候,并且当数据改变的时候,才会立马执行,这个钩子函数获取dom的内容是更新之前的内容
-
updated
在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。这个钩子函数获取 dom 的内容是更新之后的内容生成新的虚拟 dom,新的虚拟 dom 与之前的虚拟 dom 进行比对,差异之后,就会进行真实 dom 渲染。在 updated 钩子函数里面就可以获取到因 diff 算法比较差异得出来的真实 dom 渲染了。
-
beforeDestroy
实例销毁之前调用。
在这一步,实例仍然完全可用。当组件销毁的时候,就会触发这个钩子函数代表销毁之前,可以做一些善后操作,可以清除一些初始化事件、定时器相关的东西。
-
destroyed
实例销毁后调用。
该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。Vue 实例失去活性,完全丧失功能
slot
- 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ==> 子组件
- 分类:默认插槽、具名插槽、作用域插槽
<slot>
元素作为组件模板之中的内容分发插槽。<slot>
元素自身将被替换- 在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即
v-slot
指令)。它取代了slot
和slot-scope
这两个目前已被废弃但未被移除且仍在文档中的 attribute。
默认插槽
<!-- 子组件 son -->
<template>
<div class="box">
<slot>我是默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
<!-- 父组件 -->
<template>
<div>
<son> Hello!</son>
</div>
</template>
具名插槽
给 slot 增加 name 属性即可设置为具名插槽
使用时可用:
v-slot:xxx
或#xxx
,只能添加在<template>
上(推荐)slot="xxx"
可以用在一个普通元素上(不推荐,自2.6.0起已被废弃)
<!-- 子组件 son -->
<template>
<div class="box">
<div>
<slot name="header"></slot>
</div>
<div>
<slot name="footer"></slot>
</div>
</div>
</template>
<!-- 父组件 -->
<template>
<div>
<son>
<!-- 推荐写法 -->
<template v-slot:header>
我是头部
</template>
<template #footer>
我是头部
</template>
<!-- 不推荐写法 -->
<p slot="header">我是头部</p>
</son>
</div>
</template>
作用域插槽
让插槽可以访问到子组件中的内容
使用时可用:
v-slot:插槽名="自定义变量名"
或#插槽名="自定义变量名"
(推荐)slot-scope="自定义变量名"
(不推荐,自2.6.0起已被废弃)
<!-- 子组件 son -->
<template>
<div class="box">
<slot :info="info" address="123"></slot>
</div>
</template>
<!-- 父组件 -->
<template>
<div>
<!-- 推荐写法一 利用v-slot: -->
<son v-slot:default="slotProps">
{{slotProps.info}}
{{slotProps.address}}
</son>
<!-- 推荐写法二 利用# -->
<son #default="slotProps">
{{slotProps.info}}
{{slotProps.address}}
</son>
<!-- 推荐写法三 (利用解构,可以给解构值起别名和默认值) -->
<son v-slot:default="{info:userInfo, address='默人地址'}">
{{userInfo}}
{{address}}
</son>
<!-- 不推荐写法,自2.6.0后已废弃 -->
<son slot-scope="slotProps">
{{slotProps.info}}
{{slotProps.address}}
</son>
</div>
</template>
组件间通信方式
- props / $emit
- props 父 ===> 子
- $emit 子 ===> 父
- EventBus 事件总线
任意组件间的通信// 1. 创建事件总线 import Vue from 'vue' const bus = new Vue() Vue.prototype.$bus = bus // 2. 发布事件 this.$bus.$emit('eventName', 'hello') // 3. 订阅事件 this.$bus.$on('eventName', (msg)=>{ console.log(msg) // hello }) // 4. 取消订阅 destoryed(){ this.$bus.$off() // 取消所有的事件订阅 this.$bus.$off('eventName') // 取消指定事件名 this.$bus.$off('eventName',()=>{}) // 取消指定事件名的指定回调 }
- vuex
$attrs / $listeners
$attrs
父 ===> 所有子孙- 从父组件传给自定义子组件的属性,如果没有 prop 接收会自动设置到子组件内部的最外层标签上,如果是 class 和 style 的话,会合并最外层标签的 class 和 style。
- 如果子组件中不想继承父组件传入的非 prop 属性,可以使用 inheritAttrs 禁用继承,然后通过 v-bind=“$attrs” 把外部传入的 非 prop 属性设置给希望的标签上,但是这不会改变 class 和 style。
<!-- 父节点 --> <template> <div> <son name="Tom" :age={18} address="123"></son> </div> </template> <!-- 子节点 son --> <template> <div> <span>name: {{$attrs.name}}</span> <span>age: {{age}}</span> <span>address: {{$attrs.address}}</span> <!-- 孙子节点 使用v-bind="$attrs"可以继续传递父节点的属性 --> <grand-son v-bind="$attrs"></grand-son> </div> </template> <script> export default { props: ['age'], inheritAttrs: false, // 这里记得加上这个属性,并设置为false created(){ console.log(this.$attrs) // {name: 'Tom', address: '123'} } } </script>
$listeners
所有子孙 ===> 父- 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。
- 它可以通过 v-on=“$listeners” 传入内部组件——在创建更高层次的组件时非常有用。
<!-- 父节点 --> <template> <div> <son @changeValue="changeValue" @changeData="changeData"/> </div> </template> <!-- 子节点 son --> <template> <div> <input @change="$listeners.changeValue"></input> <!-- 孙子节点 使用v-on="$listeners"可以把子节点中所有的事件都传给父节点 --> <grand-son v-on="$listeners"></grand-son> </div> </template> <!-- 孙子节点 grand-son --> <template> <button @click="$listeners.changeData('hello')">提交</button> </template>
-
provide / inject
父 ===> 所有子孙
父组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。
主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。provide 和 inject 绑定并不是可响应的。如果传入了一个可监听的对象,那么其对象的 property 还是可响应的。比如传递对象等
<!-- 父组件 --> <div> <son></son> </div> <script> export default { provide(){ return { name: this.name } } data(){ return { name: 'Tom' } } } </script> <!-- 子组件 son --> <div> <grand-son></grand-son> </div> <!-- 孙子组件 grand-son--> <div> {{name}} </div> <script> export default { inject: ['name'] } </script>
class/style的动态绑定
动态绑定 class
- 对象
<div :class="{ 'active': isActive, 'text-red': isRed }"></div>
-
数组
2.1 单纯数组
<div :class="[isActive, isRed]"></div> <script> export default { data(){ return { isActive: 'active', isRed: 'text-red' } } } </script>
2.2 三元运算
<div :class="[isActive ? 'active' : '', isRed ? 'text-red' : '']"></div>
动态绑定 style
- 对象
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
- 数组
<div :style="[baseStyles, overridingStyles]"></div>
vue检测数据变化的原理
1. 数据监听器(Observer)
- 目的:能够对数据对象进行监听,即当读取或者修改数据属性的时候,能够追踪到变化。
- 实现方式:通过
Object.defineProperty()
来劫持各个属性的 get 和 set,在数据被读的时候,触发 get 方法,执行 Dep 来收集依赖,也就是收集 Watcher。注意事项
新增属性和删除属性都无法追踪到
2. 依赖收集器(Dep)
- 目的:当数据发生变化时,会将该数据及其依赖收集起来,当依赖项发生变化时,会通知依赖项,从而通知依赖它的组件。
- 实现方式:
在get中进行依赖收集,在set中通所有依赖去执行更新
。
3. 依赖(Watcher)
- watch、computed 这些都是依赖
虚拟 DOM 和 diff 算法
vue 通过模版编译生成虚拟 DOM 树,然后在通过渲染器渲染成真实 DOM,当数据更新时,产生新的虚拟dom 树,如果直接用新的虚拟 DOM 树生成真实 DOM 并不是最优的方法。为了进一步降低找出差异的性能的性能消耗,就要使用 diff 算法
虚拟DOM
是一棵以 JavaScript 对象作为基础的树,每一个节点称为 VNode ,用对象属性来描述节点,实际上它是一层对真实 DOM 的抽象,最终可以通过渲染操作使这棵树映射到真实环境上,简单来说 Virtual DOM 就是一个 Js 对象,用以描述整个文档
优势
- 跨平台优势,虚拟 DOM 并不依赖于浏览器平台,可以被应用于跨平台的开发中
- 提高性能,由于虚拟 DOM 只更新必要的部分,减少了对真实 DOM 的操作次数,可以大幅提高性能
- 开发效率高,通过使用虚拟 DOM,我们可以更加专注于业务逻辑的开发,不需要过多地关心 DOM 操作的细节
- 组件化开发,通过将一个页面拆分成多个组件,每个组件都维护自己的虚拟 DOM 树,方便复用和维护
缺点
- 学习成本高,虚拟 DOM 需要掌握一定的 Vue 知识和编程技巧,因此学习成本相对较高
- 代码量大,由于虚拟 DOM 需要构建抽象 DOM 树,因此会增加一定的代码量和复杂度
- 首次渲染效率低,在首次渲染页面时,需要构建完整的虚拟 DOM 树,因此首次渲染的效率相对较低
diff算法
diff 算法是一种对比算法。对比两者是旧虚拟 DOM 和新虚拟 DOM,对比出是哪个虚拟节点更改了,找出这个虚拟节点,并只更新这个虚拟节点所对应的真实节点,实现精准地更新真实 DOM。
在vue2 中采用双端diff算法
,核心是通过新前与旧前
、新后与旧后
、新后与旧前
、新前与旧后
、暴力比对
5 种查找 参考链接
在 vue3 中采用快速diff算法
,核心是先进行预处理
,再利用source
数组,相比双端 diff,快速 Diff 需要处理的边际条件会更少 参考链接
this.$set()
作用
给响应式对象添加新的属性。
如果我们在创建实例以后,再在实例上绑定新属性,vue 是无法进行双向绑定的
<script>
export default {
data() {
retrun {
form: {
name: 'Tom',
age: 18
}
}
},
mounted() {
// 不会触发响应式
this.form.sex = '男'
}
}
</script>
sex 属于 form 的一个新属性,而 vue 的原理是,在创建实例的时候,遍历 data 里的值,监听getter
和setter
方法,一旦
这些值更新了,就去触发对应的视图更新。而 sex 并不是 vue 实例化的时候拥有的属性,所以我们新增这个属性,vue 并没有对
他的setter
和getter
方法进行监听,因此无法实现双向绑定,所以此时需要使用this.$set(this.form, 'sex', '男')
方法进行双向绑定
用法
this.$set(obj, key, value)
- obj: 要添加属性的目标对象
- key: 要添加的属性名
- value: 要添加的属性值