Vue 3 生命周期钩子在setup中的使用
1. 生命周期钩子概述
生命周期钩子是Vue组件在其生命周期不同阶段自动调用的函数,用于执行特定的逻辑。在Vue 3组合式API中,生命周期钩子的名称与选项式API类似,但前面加上了on前缀。
1.1 为什么需要生命周期钩子
生命周期钩子允许开发者在组件的不同阶段执行特定的逻辑,例如:
- 在组件挂载后初始化数据
- 在组件更新前执行某些操作
- 在组件卸载前清理资源
- 在捕获到错误时执行处理逻辑
1.2 组合式API生命周期钩子的特点
- 函数式调用:通过导入的函数调用,而不是作为组件选项
- 在setup函数中使用:只能在setup函数内部调用
- 支持多个同名钩子:可以在setup函数中多次调用同一个生命周期钩子,它们会按顺序执行
- 更细粒度的控制:提供了更多的生命周期钩子,如
onRenderTracked和onRenderTriggered
2. 常用生命周期钩子
2.1 组件创建阶段
| 组合式API钩子 | 执行时机 | 用途 |
|---|---|---|
| setup | 组件实例创建之前 | 初始化组件状态和逻辑 |
2.2 组件挂载阶段
| 组合式API钩子 | 执行时机 | 用途 |
|---|---|---|
| onBeforeMount | 组件挂载到DOM之前 | 访问DOM节点前的准备工作 |
| onMounted | 组件挂载到DOM之后 | 初始化DOM相关操作、API请求等 |
2.3 组件更新阶段
| 组合式API钩子 | 执行时机 | 用途 |
|---|---|---|
| onBeforeUpdate | 组件更新之前 | 访问更新前的DOM状态 |
| onUpdated | 组件更新之后 | 访问更新后的DOM状态、执行DOM操作 |
2.4 组件卸载阶段
| 组合式API钩子 | 执行时机 | 用途 |
|---|---|---|
| onBeforeUnmount | 组件卸载之前 | 清理资源、取消事件监听、清除定时器等 |
| onUnmounted | 组件卸载之后 | 执行最终清理工作 |
2.5 错误处理阶段
| 组合式API钩子 | 执行时机 | 用途 |
|---|---|---|
| onErrorCaptured | 捕获到子组件错误时 | 处理错误、防止应用崩溃 |
| onRenderTracked | 组件渲染时跟踪依赖 | 调试响应式依赖 |
| onRenderTriggered | 组件重新渲染时触发 | 调试响应式更新 |
3. 生命周期钩子的使用方法
在setup函数中使用生命周期钩子,需要先从Vue中导入相应的钩子函数,然后在setup函数内部调用。
3.1 基本使用示例
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'
export default {
setup() {
const count = ref(0)
let timer = null
// 组件挂载后执行
onMounted(() => {
console.log('组件已挂载')
timer = setInterval(() => {
count.value++
}, 1000)
})
// 组件更新后执行
onUpdated(() => {
console.log('组件已更新')
})
// 组件卸载前执行
onBeforeUnmount(() => {
console.log('组件即将卸载')
clearInterval(timer)
})
// 组件卸载后执行
onUnmounted(() => {
console.log('组件已卸载')
})
return {
count
}
}
}3.2 多个同名钩子的执行顺序
在setup函数中,可以多次调用同一个生命周期钩子,它们会按顺序执行:
import { onMounted } from 'vue'
export default {
setup() {
onMounted(() => {
console.log('第一个onMounted钩子')
})
onMounted(() => {
console.log('第二个onMounted钩子')
})
onMounted(() => {
console.log('第三个onMounted钩子')
})
// 输出顺序:
// 第一个onMounted钩子
// 第二个onMounted钩子
// 第三个onMounted钩子
return {
// ...
}
}
}3.3 在组合式函数中使用生命周期钩子
生命周期钩子也可以在组合式函数中使用,这使得组合式函数可以管理自己的生命周期:
// useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMousePosition() {
const x = ref(0)
const y = ref(0)
const updatePosition = (e) => {
x.value = e.clientX
y.value = e.clientY
}
onMounted(() => {
window.addEventListener('mousemove', updatePosition)
})
onUnmounted(() => {
window.removeEventListener('mousemove', updatePosition)
})
return {
x,
y
}
}
// 在组件中使用
import { useMousePosition } from './useMousePosition'
export default {
setup() {
const { x, y } = useMousePosition()
return {
x,
y
}
}
}4. 生命周期钩子的执行顺序
了解生命周期钩子的执行顺序对于正确使用它们非常重要。以下是组合式API生命周期钩子的执行顺序:
- setup
- onBeforeMount
- onMounted
- onBeforeUpdate
- onUpdated
- onBeforeUnmount
- onUnmounted
4.1 与选项式API生命周期钩子的执行顺序对比
当组合式API和选项式API共存时,生命周期钩子的执行顺序如下:
- setup
- beforeCreate (选项式API)
- created (选项式API)
- onBeforeMount
- beforeMount (选项式API)
- onMounted
- mounted (选项式API)
- onBeforeUpdate
- beforeUpdate (选项式API)
- onUpdated
- updated (选项式API)
- onBeforeUnmount
- beforeUnmount (选项式API)
- onUnmounted
- unmounted (选项式API)
4.2 执行顺序示例
import { onBeforeMount, onMounted } from 'vue'
export default {
beforeCreate() {
console.log('选项式API: beforeCreate')
},
created() {
console.log('选项式API: created')
},
beforeMount() {
console.log('选项式API: beforeMount')
},
mounted() {
console.log('选项式API: mounted')
},
setup() {
console.log('组合式API: setup')
onBeforeMount(() => {
console.log('组合式API: onBeforeMount')
})
onMounted(() => {
console.log('组合式API: onMounted')
})
return {
// ...
}
}
}
// 输出顺序:
// 组合式API: setup
// 选项式API: beforeCreate
// 选项式API: created
// 组合式API: onBeforeMount
// 选项式API: beforeMount
// 组合式API: onMounted
// 选项式API: mounted5. 生命周期钩子的最佳实践
5.1 合理选择生命周期钩子
根据需要执行的逻辑,选择合适的生命周期钩子:
- 初始化数据和API请求:使用
onMounted - DOM操作:使用
onMounted或onUpdated - 清理资源:使用
onBeforeUnmount - 调试响应式:使用
onRenderTracked和onRenderTriggered - 错误处理:使用
onErrorCaptured
5.2 避免在生命周期钩子中执行耗时操作
避免在生命周期钩子中执行耗时操作,以免阻塞组件的渲染:
// 好的做法:在onMounted中使用异步函数
onMounted(async () => {
try {
const data = await fetchData()
// 处理数据
} catch (error) {
// 处理错误
}
})
// 不好的做法:在onMounted中执行同步耗时操作
onMounted(() => {
// 耗时的同步操作
for (let i = 0; i < 1000000; i++) {
// 复杂计算
}
})5.3 总是清理资源
在组件卸载前,总是清理资源,如事件监听器、定时器、WebSocket连接等:
import { onMounted, onBeforeUnmount } from 'vue'
export default {
setup() {
let timer = null
let ws = null
onMounted(() => {
// 启动定时器
timer = setInterval(() => {
// 执行操作
}, 1000)
// 建立WebSocket连接
ws = new WebSocket('ws://example.com')
ws.onmessage = (event) => {
// 处理消息
}
})
onBeforeUnmount(() => {
// 清理定时器
clearInterval(timer)
// 关闭WebSocket连接
if (ws) {
ws.close()
}
})
return {
// ...
}
}
}5.4 避免在onUpdated中修改状态
在onUpdated钩子中修改状态可能会导致无限循环:
// 好的做法:避免在onUpdated中修改状态
onUpdated(() => {
// 只读取状态,不修改
console.log('组件已更新')
})
// 不好的做法:在onUpdated中修改状态
onUpdated(() => {
count.value++ // 这会导致无限循环
})5.5 使用组合式函数管理生命周期
将相关的生命周期逻辑封装到组合式函数中,提高代码复用性:
// useWebSocket.js
import { ref, onMounted, onBeforeUnmount } from 'vue'
export function useWebSocket(url) {
const messages = ref([])
let ws = null
const connect = () => {
ws = new WebSocket(url)
ws.onopen = () => {
console.log('WebSocket连接已建立')
}
ws.onmessage = (event) => {
messages.value.push(JSON.parse(event.data))
}
ws.onclose = () => {
console.log('WebSocket连接已关闭')
}
ws.onerror = (error) => {
console.error('WebSocket错误:', error)
}
}
const send = (message) => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(message))
}
}
const close = () => {
if (ws) {
ws.close()
}
}
onMounted(() => {
connect()
})
onBeforeUnmount(() => {
close()
})
return {
messages,
send,
connect,
close
}
}
// 在组件中使用
import { useWebSocket } from './useWebSocket'
export default {
setup() {
const { messages, send } = useWebSocket('ws://example.com')
return {
messages,
send
}
}
}6. 特殊生命周期钩子
6.1 onErrorCaptured
onErrorCaptured钩子用于捕获子组件传递的错误,它接收三个参数:
err:错误对象instance:发生错误的组件实例info:错误信息,包含错误发生的位置
import { onErrorCaptured } from 'vue'
export default {
setup() {
onErrorCaptured((err, instance, info) => {
console.error('捕获到错误:', err)
console.log('错误组件:', instance)
console.log('错误信息:', info)
// 返回true表示阻止错误继续向上传播
return true
})
return {
// ...
}
}
}6.2 onRenderTracked和onRenderTriggered
这两个钩子用于调试响应式系统,它们只在开发模式下工作:
onRenderTracked:当组件渲染时跟踪到响应式依赖时调用onRenderTriggered:当组件因响应式依赖变化而重新渲染时调用
import { ref, onRenderTracked, onRenderTriggered } from 'vue'
export default {
setup() {
const count = ref(0)
onRenderTracked((event) => {
console.log('跟踪到依赖:', event)
// event包含:key, target, type
})
onRenderTriggered((event) => {
console.log('触发更新:', event)
// event包含:key, target, type, newValue, oldValue
})
const increment = () => {
count.value++
}
return {
count,
increment
}
}
}7. 生命周期钩子与响应式系统
生命周期钩子与响应式系统紧密结合,可以在钩子内部访问和修改响应式数据:
import { ref, onMounted, watch } from 'vue'
export default {
setup() {
const count = ref(0)
const data = ref(null)
onMounted(async () => {
// 在onMounted中获取数据
data.value = await fetchData()
})
// 监听数据变化
watch(data, (newData) => {
if (newData) {
console.log('数据已加载:', newData)
}
})
return {
count,
data
}
}
}8. 生命周期钩子的性能考虑
8.1 避免不必要的生命周期钩子
只使用必要的生命周期钩子,避免在每个组件中都使用所有钩子:
// 好的做法:只使用必要的钩子
import { onMounted, onBeforeUnmount } from 'vue'
export default {
setup() {
let timer = null
onMounted(() => {
timer = setInterval(() => {
// 执行操作
}, 1000)
})
onBeforeUnmount(() => {
clearInterval(timer)
})
return {
// ...
}
}
}
// 不好的做法:使用不必要的钩子
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
export default {
setup() {
// 只使用了onMounted和onBeforeUnmount,其他钩子都是不必要的
onBeforeMount(() => {
// 空实现
})
onMounted(() => {
// 执行逻辑
})
onBeforeUpdate(() => {
// 空实现
})
onUpdated(() => {
// 空实现
})
onBeforeUnmount(() => {
// 执行逻辑
})
onUnmounted(() => {
// 空实现
})
return {
// ...
}
}
}8.2 优化onMounted中的异步操作
在onMounted中执行异步操作时,可以使用await来优化代码:
// 好的做法:使用await优化异步操作
onMounted(async () => {
try {
const data1 = await fetchData1()
const data2 = await fetchData2()
// 处理数据
} catch (error) {
// 处理错误
}
})
// 好的做法:并行执行异步操作
onMounted(async () => {
try {
const [data1, data2] = await Promise.all([
fetchData1(),
fetchData2()
])
// 处理数据
} catch (error) {
// 处理错误
}
})9. 实战练习
练习1:使用生命周期钩子管理定时器
创建一个组件,使用onMounted启动一个定时器,每隔1秒更新一次计数器,然后在onBeforeUnmount中清理定时器。
练习2:使用生命周期钩子管理API请求
创建一个组件,在onMounted中发起API请求获取数据,在onBeforeUnmount中取消请求(如果请求尚未完成)。
练习3:使用组合式函数管理生命周期
创建一个组合式函数useLocalStorage,用于在组件挂载时从localStorage读取数据,在组件更新时将数据保存到localStorage,并在组件卸载时执行清理工作。
10. 总结
组合式API的生命周期钩子提供了一种灵活的方式来管理组件的生命周期,它们具有以下特点:
- 函数式调用,在setup函数内部使用
- 支持多个同名钩子,按顺序执行
- 与选项式API生命周期钩子共存时,组合式API钩子先执行
- 提供了更多的调试钩子,如
onRenderTracked和onRenderTriggered - 可以封装到组合式函数中,提高代码复用性
掌握生命周期钩子的使用方法和最佳实践,是编写可靠、高效Vue组件的关键。通过合理使用生命周期钩子,可以在组件的不同阶段执行特定的逻辑,管理资源,处理错误,以及优化性能。
11. 扩展阅读
通过本集的学习,我们深入理解了Vue 3组合式API中生命周期钩子的使用方法和最佳实践。在下一集中,我们将学习provide与inject依赖注入,这是组合式API中组件间通信的重要方式。