Vue 随笔
本文主要记录一些关于Vue的API使用方法和一些底层的原理,供自己以后查漏补缺,也欢迎同道朋友交流学习。
vue介绍
Vue
是一个现代化的开源渐进式 JavaScript
框架,专为构建用户界面而设计。Vue 的核心库聚焦于视图层,易于学习且集成到现有项目中,同时也能够驱动复杂的单页应用程序(SPA
)的开发。Vue 凭借其响应式的数据管理、组件化的开发方式、声明式的模板、轻量高效、易学易用以及强大的生态支持,成为了现代 Web 开发中一个非常有吸引力的选择。
核心特点
响应式数据绑定
:Vue 遵循MVVM
( Model-View-ViewModel )设计模式,其中ViewModel
作为连接模型和视图的桥梁,实现了数据的双向绑定。当数据模型发生变化时,相关的 DOM 会自动更新,确保数据和视图的一致性。这一特性极大简化了状态管理,开发者无需手动操作 DOM。渐进式框架
:Vue 被设计为可以逐步采用的框架。开发者可以从简单地增强现有页面的一部分开始,逐渐过渡到构建复杂的单页应用程序(SPA)。这种灵活性使得 Vue 既适合小型项目也适合大型企业级应用。组件化
:Vue 鼓励采用组件化开发方式,允许开发者将用户界面划分为可复用的组件。每个组件都包含自己的 HTML 模板、CSS 样式和 JS 逻辑,有利于代码的组织和维护。虚拟DOM
:Vue 利用虚拟 DOM 来提高性能。虚拟 DOM 是对实际 DOM 结构的轻量级抽象,可以在内存中快速地进行计算和对比,仅在必要时以最小的开销更新实际 DOM,从而提高页面渲染速度。指令系统
:Vue 提供了一套指令(如 v-if、v-for、v-bind、v-on 等),这些指令用于操作数据和 DOM,使得开发者能够以声明式的方式编写动态视图。声明式模板
:Vue 使用模板语法来声明式地描述应用程序的UI结构和行为。开发者只需关注描述最终状态,Vue 会负责处理数据变化到界面更新的过程,使代码更易于阅读和理解。工具链支持
:Vue 拥有丰富的生态系统,包括Vue CLI
(命令行工具)、Vuex
(状态管理模式)、Vue Router
(路由管理器)等,这些工具可以帮助开发者高效地构建大型应用。轻量级与高性能
:Vue 的核心库非常轻量,压缩后的文件大小约为 30KB 左右,这使得 Vue 易于加载且对性能影响小。
安装
官网:npm create vue@latest
版本变动
vue2
- 引入了
Virtual DOM
,显著提升了性能; - 加强了组件系统,增加了异步组件、组件生命周期钩子的调整等;
- 支持了
SSR
(服务器端渲染); - 引入
Vuex
和Vue Router
作为状态管理和路由的官方解决方案。
vue3
- Composition API:引入了一个新的
组合式 API
,提供了更灵活的数据和逻辑组织方式。 - Improved Performance:性能大幅提升,包括更快的解析速度和更小的运行时大小。
- Teleport & Fragments:新增特性,如
Teleport
用于跨组件插入 DOM 节点,Fragments
支持返回多个根节点。 - 更好的TypeScript支持:Vue3 原生支持
TypeScript
,使得类型安全更佳。 - 改进的插件系统和自定义渲染API:使得扩展 Vue 变得更加灵活。
生命周期
Vue 的生命周期可以分为三个主要阶段,每个阶段都包含特定的生命周期钩子函数,这些函数在应用的不同时间点被自动调用,允许开发者在特定时刻运行代码。尽管要注意 Vue3 引入了 Composition API
,生命周期钩子的使用方式有所变化,但核心概念相似。
初始化阶段
beforeCreate
:实例刚被创建,数据观测(data observer
)和事件配置(watcher/event
)还未初始化,此时无法访问到 data、computed、watch 等属性和方法。created
:Vue 实例已经创建完成,完成了数据观测、属性和方法的初始化。在这个阶段,你可以访问到 data、computed 等属性,但DOM 还未生成,因此不能访问到真实的DOM元素。
挂载阶段
beforeMount
:在挂载之前被调用,相关的render
函数首次被调用生成虚拟 DOM,但尚未与真实 DOM 进行关联。此时,模板编译已完成,但尚未挂载到 DOM 上。mounted
:Vue 实例被挂载到 DOM 上,el 被新创建的vm.$el
替换,并且完成了渲染。此时可以访问到真实的 DOM 元素,可以执行DOM 操作或者初始化第三方库。
更新阶段
beforeUpdate
:数据变化导致虚拟 DOM 重新渲染和打补丁之前调用。此时,组件的DOM尚未更新,但是新的虚拟 DOM 已经创建完成。updated
:虚拟 DOM 的重新渲染和打补丁已经完成,组件 DOM 已经更新,可以在这里执行依赖于 DOM 的操作。但应当避免在此期间修改状态,以免触发无限循环。
销毁阶段
beforeDestroy
:Vue 实例销毁之前调用。在这一步,实例仍然完全可用,可以执行清理工作,例如取消定时器、解绑事件监听器等。destroyed
:Vue 实例已经被销毁,所有的子实例和事件监听器也被移除,此时组件的所有东西都应该被清理完毕,该钩子被调用后,Vue实例将不再可用。
Vue3 的 Composition API
Vue3 中推荐使用 Composition API 中的 onBeforeMount
、onMounted
、onBeforeUpdate
、onUpdated
、onBeforeUnmount
、onUnmounted
等对应钩子,它们在功能上与上述周期相同,但使用方式更符合组合 API 的逻辑组织方式。
父子组件的创建挂载顺序
父子组件的更新顺序
父子组件的卸载顺序
基础指令
双向绑定
v-model
v-model
是用于表单输入和数据双向绑定的一个指令,它提供了一种便捷的方式来实现视图( View )和模型( Model )之间的数据同步。v-model
本质上是一个语法糖,它结合了 v-bind
和v-on
的特性,实现了数据的自动更新。
基本用法
<template>
<div>
<input v-model="inputValue">
<p>inputValue: {{ inputValue }}</p>
</div>
</template>
<script>
export default {
data() {
return {
inputValue: '',
};
}
};
</script>
表单修饰符
Vue为 v-model
提供了几个修饰符,用于处理不同的输入类型和场景:
.trim
:在输入前过滤掉前后空白字符。.number
:将用户的输入转换为数值类型(浮点数或整数)。.lazy
:在change
事件而非每次input
事件时更新数据。
条件判断
v-if/v-else
v-if
和 v-else
是用于条件渲染的两个指令,它们可以帮助你根据表达式的真假动态地显示或隐藏元素。
<div v-if="type === 'A'">类型A的内容</div>
<div v-else-if="type === 'B'">类型B的内容</div>
<div v-else>其他类型的内容</div>
v-show
v-show
是 Vue 中的另一个条件渲染指令,与 v-if
有着相似的功能,但它们在实现方式和应用场景上有所不同。
<div v-show="isShown">我可能会显示或隐藏。</div>
v-show 与 v-if 的区别
v-show
指令用于根据表达式的值切换元素的显示或隐藏。与 v-if
不同,无论表达式的值是真是假,元素始终会被渲染并保留在DOM中。v-show
通过修改元素的CSS display
属性(通常是切换 block
和 none
)来控制元素的可见性。
- 初始渲染:
v-if
是惰性的,只有在条件首次为真时才会渲染 DOM;而v-show
则无论条件真假都会渲染 DOM 元素。 - 性能: 对于初始渲染来说,
v-if
有更高的切换开销,因为它需要等待条件改变时才进行 DOM 的创建或销毁;而v-show
的切换成本较低,因为它只是简单地切换 CSS 属性。但是,由于v-show
总是渲染 DOM,如果从未显示的元素数量很大,这可能会有轻微的内存消耗。 - 频繁切换: 如果一个元素需要频繁地切换显示状态,
v-show
通常更合适,因为它避免了 DOM 的插入和删除操作。 - SEO: 对于服务端渲染(
SSR
)的应用,v-if
在初始渲染时可以条件性地渲染部分 DOM ,可能对SEO
更加友好。
应用场景
- 当你需要在初始页面加载时就隐藏某些内容,且这些内容不太可能频繁切换显示状态时,
v-if
更为合适。 - 如果元素需要频繁切换显示状态,并且初始渲染时就应该存在于 DOM 中(比如动画效果的元素),那么应该使用
v-show
。
循环遍历
v-for
v-for
是 Vue 中一个非常强大的指令,用于遍历数组、对象或整数区间来渲染列表。在使用 v-for
时,建议为每个迭代元素提供一个唯一的 key
属性。这有助于 Vue 更高效地追踪每个节点的身份,从而优化性能,特别是在列表有大量重排或元素需要保持状态时。
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
配合v-if使用
虽然可以将 v-if
与 v-for
一起使用,但 Vue 官方建议尽可能将它们分开使用,因为 v-if
有更高的优先级,且在每次循环中都会被重新计算,可能导致不必要的性能开销。如果需要根据条件过滤列表,最好预先在计算属性或方法中完成过滤。
- vue2中v-if和v-for的优先级:在 Vue2 中,当
v-if
和v-for
一起在一个元素上使用时,v-for
具有比v-if
更高的优先级。 - vue3中v-if和v-for的优先级:在 vue3 中,
v-if
在编译阶段进行了静态节点提升,所以在v-for
遍历每个节点,v-if
对单个节点判断,这种情况会报错。
数据绑定和事件监听
在 Vue 中,v-bind
和 v-on
是两个非常重要的指令,它们分别用于数据绑定
和事件监听
,是构建动态用户界面的关键工具。
v-bind
v-bind
指令用于动态地绑定 HTML 属性、CSS类名、样式等至 Vue 实例的数据属性。这使得你可以根据 Vue 实例的数据变化自动更新 DOM 元素的属性。
<img v-bind:src="imageURL" alt="Vue Image">
<!-- 为了简化书写,`:src`是`v-bind:src`的缩写形式: -->
<img :src="imageURL" alt="Vue Image">
v-on
v-on
指令用于绑定事件监听器,当事件触发时调用 Vue 实例中定义的方法。
<button v-on:click="handleClick">点击我</button>
<!-- 和`v-bind`一样,`v-on`也有缩写形式,使用`@`符号: -->
<button @click="handleClick">点击我</button>
v-on
还支持事件修饰符,用来改变事件的行为,如.stop
阻止事件冒泡,.prevent
阻止默认行为,.capture
使用事件捕获模式,.self
仅当事件是从元素本身触发时才触发等。
<button @click.stop="handleClick">阻止冒泡</button>
状态选项
data
用于定义组件的状态(数据)。这个选项应该返回一个对象,对象的属性会自动变为响应式的。
export default {
data() {
return { a: 1 }
},
created() {
console.log(this.a) // 1
console.log(this.$data) // { a: 1 }
}
}
props
定义组件可以接收的外部传入的属性,用来实现父子组件间的数据传递。
简易声明:
export default {
props: [ 'size', 'myMessage' ]
}
对象声明,带有验证:
export default {
props: {
// 类型检查
height: Number,
// 类型检查 + 其他验证
age: {
type: Number,
default: 0,
required: true,
validator: (value) => {
return value >= 0
}
}
}
}
computed
计算属性,用于定义依赖于其它数据的衍生状态。计算属性会在依赖项变化时自动重新计算。
export default {
data() {
return { a: 1 }
},
computed: {
// 只读
aDouble() {
return this.a * 2
},
// 可写
aPlus: {
get() {
return this.a + 1
},
set(v) {
this.a = v - 1
}
}
},
created() {
console.log(this.aDouble) // => 2
console.log(this.aPlus) // => 2
this.aPlus = 3
console.log(this.a) // => 2
console.log(this.aDouble) // => 4
}
}
methods
包含组件中定义的方法(函数)。这些方法可以在模板中直接调用,用于处理逻辑或响应事件。
export default {
data() {
return { a: 1 }
},
methods: {
plus() {
this.a++
}
},
created() {
this.plus()
console.log(this.a) // => 2
}
}
watch
观察 Vue 实例上的数据变化,并在变化发生后执行特定函数。常用于执行异步操作或复杂的更新逻辑。
export default {
data() {
return {
a: 1,
b: 2,
c: {
d: 4
},
e: 5,
f: 6
}
},
watch: {
// 侦听根级属性
a (val, oldVal) {
console.log(`new: ${val}, old: ${oldVal}`)
},
// 字符串方法名称
b: 'someMethod',
// 该回调将会在被侦听的对象的属性改变时调动,无论其被嵌套多深
c: {
handler(val, oldVal) {
console.log('c changed')
},
deep: true
},
// 侦听单个嵌套属性:
'c.d': function (val, oldVal) {
// do something
},
// 该回调将会在侦听开始之后立即调用
e: {
handler(val, oldVal) {
console.log('e changed')
},
immediate: true
},
// 你可以传入回调数组,它们将会被逐一调用
f: [
'handle1',
function handle2(val, oldVal) {
console.log('handle2 triggered')
},
{
handler: function handle3(val, oldVal) {
console.log('handle3 triggered')
}
/* ... */
}
]
},
methods: {
someMethod() {
console.log('b changed')
},
handle1() {
console.log('handle 1 triggered')
}
},
created() {
this.a = 3 // => new: 3, old: 1
}
}
composition API
ref
ref
是一个核心的 Composition API
函数,用于创建响应式引用,使得开发者可以直接操作和跟踪非代理原始数据类型(如普通对象、数字、字符串等)。ref
函数接受一个初始值作为参数,并返回一个响应式引用对象。这个对象有一个 .value
属性,用于读取和修改底层的数据值。当 .value
的值发生变化时,Vue会自动追踪这些变化并触发相应的视图更新。
<script setup>
import { ref } from 'vue';
const message = ref('Hello Vue 3!');
</script>
<template>
<h1>{{ message }}</h1> <!-- 直接使用message,不需要.message -->
</template>
其他用途
- DOM引用:除了数据,
ref
也用于获取 DOM 元素的引用,与 Vue 2 中的用法类似,但在 Vue3 中通常与onMounted
等生命周期钩子结合使用。 - 组合使用:
ref
可以与其他 Composition API 函数(如reactive
、computed
等)一起使用,以构建复杂的响应式逻辑。
reactive
reactive
是另一个核心的 Composition API 函数,它用于创建一个可响应的对象,使得对象的属性变化能够被 Vue 自动追踪,进而触发依赖的更新。与 ref
专注于单一值不同,reactive
适用于处理包含多个属性的对象。以下是关于 reactive
的一些关键点和用法:
reactive特点
- 创建响应式对象:
reactive
函数接收一个普通对象作为参数,并返回一个新的响应式代理对象。这个代理对象的属性是响应式的,当属性值被修改时,Vue会自动记录依赖关系,并在适当的时机触发相关依赖的更新。 - 深层次响应:与
ref
相比,reactive
提供了深层次的响应性,意味着嵌套对象的属性也能自动成为响应式的,而不需要额外的包装。
<script setup>
import { reactive } from 'vue';
const state = reactive({
count: 0,
user: { name: 'Vue User', age: 25 }
});
</script>
<template>
<p>{{ state.count }}</p>
<p>{{ state.user.name }}</p>
</template>
注意事项
- 不改变引用:修改
reactive
对象的属性值不会改变对象的引用,这在某些需要基于对象变更触发逻辑的场景下需要注意。 - 避免直接替换对象:直接用一个新的对象替换响应式对象的属性会导致失去响应性,应该使用
reactive
或toRef
、toRefs
等API来正确处理。
与ref的区别
- 数据结构:
ref
用于单个值,如数字、字符串或布尔值,而reactive
用于处理对象或数组这样的复杂数据结构。 - 访问方式:
ref
创建的响应式对象需要通过.value
访问实际值,而reactive
创建的响应式对象直接通过属性访问。 - 响应深度:
reactive
提供深层次响应性,适合处理对象内部属性的变化。
toRefs
toRefs
是 Vue3 中提供的一个辅助函数,它的主要作用是将一个由 reactive
创建的响应式对象转换为一组普通的对象属性引用( refs ),这样就可以在模板或普通 JS 代码中直接访问这些属性,而不需要通过 .value
。这对于想要在模板中直接使用来自reactive
对象的属性时非常有用,同时保持了对这些属性的响应式更新能力。
<script setup>
import { reactive, toRefs } from 'vue'
const userState = reactive({ name: 'Vue User', age: 25 })
// 将userState转换为refs
const userRefs = toRefs(userState)
</script>
<template>
<h1>Hello, {{ userRefs.name }}!</h1>
<p>You are {{ userRefs.age }} years old.</p>
</template>
注意事项:
toRefs
返回的是一个新的对象,其中每个属性都是一个ref
,指向原reactive
对象相应属性的.value
。- 使用
toRefs
时,注意不要直接修改返回对象的结构(比如添加或删除属性),因为这可能会导致预期之外的行为。 - 当你需要将整个
reactive
对象暴露给模板,但又想直接访问其内部属性时,toRefs
是非常有用的。
watch watchEffect watchPostEffect watchSyncEffect
在 Vue3 中,watch
, watchEffect
, watchPostEffect
, 和 watchSyncEffect
是用于响应式数据变化并执行副作用函数的一系列 API。选择合适的 API 取决于具体需求,比如是否需要立即执行、执行时机、是否有特定的清理需求等。
1. watch
watch
API 允许你观察一个或多个响应式数据源的变化,并在变化发生时执行一个回调函数。它比其他几个 API 提供了更多的控制选项,如可以选择是否立即执行回调、是否深度监听等。
import { watch } from 'vue'
export default {
setup() {
let count = ref(0)
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
}
}
特点:
- 可以指定是否深度监听(
deep: true
)、是否立即执行初始回调(immediate: true
)等选项。 - 提供了清理函数( cleanup function ),可以用来清理副作用,例如取消定时器或取消网络请求。
2. watchEffect
watchEffect
是一个更加简洁的 API,它会自动追踪依赖于其回调函数中的所有响应式数据,并在这些数据变化时重新执行回调。它总是立即执行,并且默认开启深度监听。
import { watchEffect } from 'vue'
export default {
setup() {
let count = ref(0)
watchEffect(() => {
console.log(`count is now ${count.value}`)
})
}
}
特点:
- 不需要显式指定依赖,Vue会自动收集依赖并执行。
- 每次执行后都会清除上一次的副作用,适合执行无副作用的逻辑,比如数据计算。
3. watchPostEffect
watchPostEffect
类似于 watchEffect
,但它会在所有的同步副作用执行完毕后,下一个微任务队列中执行。这适用于那些需要在所有状态更新完成后才执行的逻辑。
import { watchPostEffect } from 'vue'
export default {
setup() {
let count = ref(0)
watchPostEffect(() => {
console.log('This runs after all sync effects have been applied.')
})
}
}
特点:
- 确保在所有同步副作用之后执行,适合做最终的调整或日志记录。
4. watchSyncEffect
watchSyncEffect
确保副作用函数在当前的同步更新周期中执行。这与 watchEffect
的主要区别在于它不等待微任务队列,而是直接在当前堆栈中执行,适用于需要立即响应的场景。
特点(理论上的理解):
- 立即执行,并确保在当前的渲染周期内完成。
- 由于 Vue3 的机制,大部分情况下
watchEffect
已经足够满足即时响应的需求。
组件通信
Vue 中的组件通信是构建复杂应用的关键部分,它允许组件之间共享数据和功能。Vue提供了多种方式来实现组件间通信,以下是一些常见的方法:
Props 和 $emit
(父子组件通信)
- Props: 父组件通过属性向子组件传递数据。
- $emit: 子组件通过触发事件(自定义事件)并将数据作为参数传递给父组件,父组件再通过监听这些事件来接收数据。
父组件 (ParentComponent.vue)
<template>
<div>
<button @click="incrementCounter">增加计数</button>
<ChildComponent :counter="counter" @incremented="handleIncremented"/>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
counter: 0
};
},
methods: {
incrementCounter() {
this.counter++;
},
handleIncremented(newValue) {
this.counter = newValue;
}
}
};
</script>
子组件 (ChildComponent.vue)
<template>
<div>
<p>当前计数: {{ counter }}</p>
<button @click="$emit('incremented', counter + 1)">子组件增加计数</button>
</div>
</template>
<script>
export default {
props: {
counter: Number
}
};
</script>
Provide / Inject(祖先后代通信)
- 用于祖先组件向后代组件注入数据,而不必显式地通过每一个中间组件传递props。这种方式适用于祖先组件和子孙组件之间跨级传递数据。
祖先组件
<template>
<div>
<h2>我是祖先组件</h2>
<slot></slot>
</div>
</template>
<script>
export default {
provide() {
return {
message: '这是来自祖先的信息',
config: { theme: 'dark', version: '1.0' }
};
}
};
</script>
后代组件
<template>
<div>
<h3>我是后代组件</h3>
<p>接收到的信息:{{ message }}</p>
<p>配置主题:{{ config.theme }}</p>
</div>
</template>
<script>
export default {
inject: ['message', 'config']
};
</script>
Vuex(状态管理)
Vuex
是 Vue 的状态管理模式,适用于大型应用中多个组件共享状态的情况。通过创建一个全局唯一的 store 来存储和管理状态,组件可以通过store 的 getter 获取状态,通过mutations
或actions
来改变状态。
Event Bus(事件总线)
- 创建一个全局的
Event Bus
(事件总线),组件之间可以通过它发布和订阅事件来通讯。Vue3 鼓励使用更结构化的通信方式,如 Composition API 中的 provide/inject 或直接在组件外部管理共享状态,减少对全局 Event Bus 的依赖。
$attrs
和 $listeners
- 在组件间传递非 prop 属性和事件监听器,常用于封装可复用组件时,使组件能透传未被自身识别的属性和事件。
<!-- WrapperComponent.vue -->
<template>
<div class="wrapper">
<!-- 使用 v-bind="$attrs" 透传所有未被声明的属性 -->
<slot v-bind="$attrs"></slot>
<!-- 使用 v-on="$listeners" 透传所有事件 -->
<slot v-on="$listeners"></slot>
</div>
</template>
内置组件
Transition
Transition
是一个内置组件,这意味着它在任意别的组件中都可以被使用,无需注册。它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。进入或离开可以由以下的条件之一触发:
- 由
v-if
所触发的切换 - 由
v-show
所触发的切换 - 由特殊元素
component
切换的动态组件 - 改变特殊的
key
属性
<button @click="show = !show">Toggle</button>
<Transition>
<p v-if="show">hello</p>
</Transition>
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
基于 CSS 的过渡效果
v-enter-from
:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。v-enter-active
:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。v-enter-to
:进入动画的结束状态。v-leave-from
:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。v-leave-active
:离开动画的生效状态。应用于整个离开动画阶段。v-leave-to
:离开动画的结束状态。
KeepAlive
<KeepAlive>
是 Vue 的一个内置组件,主要用于缓存组件实例,避免在组件切换时重复渲染和销毁。当你希望保留组件状态或避免不必要的重新渲染时,<KeepAlive>
非常有用,常见于路由切换场景中保持组件的状态。
用法
在Vue中,你可以直接将<KeepAlive>
作为包裹组件,将其内的子组件缓存起来:
<KeepAlive>
<component :is="currentComponent"></component>
</KeepAlive>
或者,在 Vue Router
中,围绕动态路由组件使用<KeepAlive>
:
<KeepAlive>
<router-view></router-view>
</KeepAlive>
包含与排除
<KeepAlive>
提供了两个属性来控制哪些组件应该被缓存:include
和 exclude
。这两个属性接受一个组件名称的字符串数组或正则表达式,用于指定哪些组件应该被缓存或不应该被缓存。
<KeepAlive include="ComponentA, ComponentB">
<router-view></router-view>
</KeepAlive>
或者:
<KeepAlive exclude="ComponentC">
<router-view></router-view>
</KeepAlive>
生命周期钩子
被<KeepAlive>
包裹的组件会有额外的生命周期钩子:
activated
:组件被激活时调用,当组件被切换到且之前已被缓存时。deactivated
:组件被停用时调用,当组件被切换离开但依然被缓存时。
这意味着常规的 mounted
和 destroyed
钩子在首次加载和完全移除组件时才会被调用,而 activated
和 deactivated
用于处理组件的可见性变化。
Teleport
<Teleport>
为 Vue 开发人员提供了一种强大的方式来控制组件的渲染位置,使得 UI 组件的构建更加灵活和高效,尤其是在处理全局弹窗、提示信息等需要脱离当前组件层级结构的场景中。通过合理使用 <Teleport>
,可以简化复杂 UI 的设计和实现,提升用户体验。
改写 MyModal 示例:
<button @click="open = true">Open Modal</button>
<Teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>
底层原理
模板编译
Vue
将模板编译成渲染函数,这个过程包括分析模板结构、收集依赖关系以及生成高效的更新代码。Vue 使用了编译器来预编译模板,使其在运行时能够高效地计算和更新 DOM
。
虚拟DOM
虚拟 DOM 是一个轻量级的内存中表示的实际 DOM 结构的树形结构。当数据变化时,Vue 会先在虚拟 DOM 上计算出最小的更新差异,然后将这些差异应用到实际 DOM 上,而不是直接操作实际 DOM,从而减少了不必要的 DOM 操作,提高了性能。
响应式原理
Vue 的核心是一个响应式系统,它让数据模型和视图能够保持同步。Vue 使用 Object.defineProperty
方法(在 Vue2 中)或 Proxy
(在 Vue3 中)来劫持数据对象的访问器属性,将所有数据转化为 getter
和 setter
。当数据被访问时( getter 触发),Vue 会跟踪依赖这个数据的计算属性和侦听器;当数据发生变化( setter 触发),Vue 会通知所有依赖这个数据的组件进行更新。
观察者模式(Watcher)
Vue 利用 观察者模式
来监听数据变化。每个组件都有一个对应的 Watcher
实例,当数据发生变化时,Watcher 会触发更新,进而更新对应的视图。这个过程中涉及到了 Dep(依赖收集器)
的概念,它负责收集和管理 Watcher,当数据变化时,Dep 通知所有相关的 Watcher 执行更新操作。
发布/订阅模式
Vue 的响应式系统也体现了 发布/订阅模式
的思想。当数据变化时,订阅了这个数据的 Watcher
会收到这个消息并执行相应的更新操作。
异步队列与NextTick
Vue 使用异步队列来批量处理更新,避免了不必要的多次 DOM 更新,同时提供了 nextTick
方法来延迟执行回调,确保在 DOM 更新后执行。
双向绑定v-model
v-model
本质上是一个语法糖,它结合了:value
(绑定属性值)和@input
(监听输入事件)两个功能。当表单元素的值发生变化时, v-model 会自动更新绑定的数据;反之,当数据发生变化时,v-model也会更新表单元素的值。
Vue的diff算法
diff
算法是其 Virtual DOM
更新机制的核心部分,负责高效地计算出两棵虚拟DOM树的最小差异,并将这些差异应用到实际的 DOM 上,以最小化 DOM 操作,提升性能。Vue 的 diff 算法遵循以下原则和策略:
- 同层比较:Vue 的 diff 算法主要是同层级的比较,即只在同一层级的节点之间进行比较,不考虑跨层级的移动。这意味着 Vue 会遍历新旧节点树的同一层级,寻找变更的地方。如果在某一层发现了差异,Vue 会停止比较当前层级的后续节点,转而处理这个差异,因为后续的节点可能都已发生变化。
- 首尾双向遍历:Vue3 中对 diff 算法进行了优化,采用了首尾双向遍历的策略,也称为“
双端 diff
”。这种策略在比较子节点时,同时从两端开始向中间遍历,这样可以更快地定位到中间发生变化的节点,尤其是在列表项的顺序发生改变时,能够更高效地找到差异并进行移动或添加删除操作。 - key属性:Vue 利用元素的 key 属性来跟踪节点的身份,这有助于 Vue 识别哪些节点是新创建的、哪些是移动的、哪些是应该被删除的。使用合适的 key 值可以显著提高 diff 算法的效率,特别是在列表渲染时。
- 最小变更:Vue 的 diff 算法致力于找出最小的变更集,仅对实际发生变化的内容进行 DOM 操作,避免不必要的 DOM 操作,比如通过记录和比对节点的 “
patch
”,只对变动的部分进行更新。 - 组件层级的diff:Vue 的 diff 不仅发生在 DOM 元素上,也发生在组件层次上。Vue 会递归地对组件的虚拟 DOM 树进行 diff,每个组件的更新都是独立进行的,确保了组件的封装性和可复用性。
- 更新策略:Vue 的 diff 算法还包括一些更新策略,比如对于文本节点的直接替换、对于新老节点类型不同的直接替换等,这些策略都是为了尽可能减少计算和操作的成本。
vue3的新特性
- Composition API: 这是 Vue3 中最重要的变化之一,提供了一种新的逻辑组织方式,使得代码更加模块化、可重用。通过
setup
函数,你可以使用如ref
、reactive
等函数来创建和管理状态,以及使用其他 Composition API 功能。 - 生命周期钩子: Vue3 对生命周期钩子进行了调整和补充,提供了新的组合式API生命周期钩子,如
onBeforeMount
、onMounted
等,以适应新的编程风格。 - 更好的性能: 通过使用
Proxy
而非Object.defineProperty
来实现响应式系统,提高了数据响应速度和效率。此外,更优化的虚拟 DOM 算法减少了不必要的 DOM 操作,提升了渲染性能。 - Tree-shaking: 支持按需导入,在构建时可以剔除未使用的代码,进一步减小最终的包体积。
- 新内置组件: 引入了
<Fragment>
、<Teleport>
和<Suspense>
等新组件。<Fragment>
允许组件返回多个根节点;<Teleport>
可以将组件内容渲染到文档的其他部分;<Suspense>
则用于处理异步加载或等待状态。 - 改进的TypeScript集成: Vue3 原生支持
TypeScript
,提供了更好的类型注解和错误检查,便于构建类型安全的应用。 - 改进的Slots: Vue3 简化了具名插槽和作用域插槽的语法,使得插槽的使用更加灵活和直观。
- 改进的DevTools: Vue3 的 DevTools 提供了更强大的调试功能,包括对 Composition API 的支持,帮助开发者更高效地诊断问题。
- 自定义渲染API: Vue3 进一步增强了自定义渲染器 API,使得 Vue 不仅可以运行在 Web 上,还能轻松适配到
Weex
等其他平台,或用于实验性的渲染技术。
性能优化
Vue 性能优化主要集中在减少DOM操作、优化渲染流程、以及有效管理资源等方面。
- 使用v-once指令:对于静态内容,使用
v-once
指令可以避免不必要的重新渲染。 - v-if vs v-show:理解
v-if
和v-show
的区别并正确使用。v-if
用于条件渲染,不会渲染不在条件分支中的 DOM;而v-show
通过 CSS 的display
属性切换元素的显示状态,适合频繁切换可见性的场景。 - 组件懒加载:对于大型应用,使用
Webpack
的动态导入或vue-router
的懒加载功能来延迟加载那些不是立即需要的组件,可以减少初始页面加载时间。 - 图片懒加载:对于长列表中的图片,使用懒加载技术,仅在图片即将进入可视区域时才开始加载。
- KeepAlive组件:利用
<KeepAlive>
包裹组件,可以缓存组件状态,避免重复渲染,适用于切换频率高的组件。 - 优化列表渲染:为列表中的每一项提供唯一的
key
属性,帮助 Vue 更高效地进行 DOM 的复用和更新。避免在列表中使用索引作为 key,尤其是在列表会动态增删的情况下。 - 节流与防抖:对于频繁触发的事件(如滚动、输入等),使用节流(
throttle
)或防抖(debounce
)技术来限制函数执行的频率,减少不必要的计算和渲染。 - 使用性能分析工具:Vue DevTools 提供了性能分析面板,可以帮助开发者识别渲染瓶颈和内存泄漏等问题。
- 升级到 Vue3:Vue3 带来了诸如
Proxy
的响应式系统、改进的虚拟 DOM 算法等性能提升,如果条件允许,升级到 Vue3 也是提升性能的一个选项。