Vue 3 生命周期钩子在setup中的使用

1. 生命周期钩子概述

生命周期钩子是Vue组件在其生命周期不同阶段自动调用的函数,用于执行特定的逻辑。在Vue 3组合式API中,生命周期钩子的名称与选项式API类似,但前面加上了on前缀。

1.1 为什么需要生命周期钩子

生命周期钩子允许开发者在组件的不同阶段执行特定的逻辑,例如:

  • 在组件挂载后初始化数据
  • 在组件更新前执行某些操作
  • 在组件卸载前清理资源
  • 在捕获到错误时执行处理逻辑

1.2 组合式API生命周期钩子的特点

  • 函数式调用:通过导入的函数调用,而不是作为组件选项
  • 在setup函数中使用:只能在setup函数内部调用
  • 支持多个同名钩子:可以在setup函数中多次调用同一个生命周期钩子,它们会按顺序执行
  • 更细粒度的控制:提供了更多的生命周期钩子,如onRenderTrackedonRenderTriggered

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生命周期钩子的执行顺序:

  1. setup
  2. onBeforeMount
  3. onMounted
  4. onBeforeUpdate
  5. onUpdated
  6. onBeforeUnmount
  7. onUnmounted

4.1 与选项式API生命周期钩子的执行顺序对比

当组合式API和选项式API共存时,生命周期钩子的执行顺序如下:

  1. setup
  2. beforeCreate (选项式API)
  3. created (选项式API)
  4. onBeforeMount
  5. beforeMount (选项式API)
  6. onMounted
  7. mounted (选项式API)
  8. onBeforeUpdate
  9. beforeUpdate (选项式API)
  10. onUpdated
  11. updated (选项式API)
  12. onBeforeUnmount
  13. beforeUnmount (选项式API)
  14. onUnmounted
  15. 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: mounted

5. 生命周期钩子的最佳实践

5.1 合理选择生命周期钩子

根据需要执行的逻辑,选择合适的生命周期钩子:

  • 初始化数据和API请求:使用onMounted
  • DOM操作:使用onMountedonUpdated
  • 清理资源:使用onBeforeUnmount
  • 调试响应式:使用onRenderTrackedonRenderTriggered
  • 错误处理:使用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钩子先执行
  • 提供了更多的调试钩子,如onRenderTrackedonRenderTriggered
  • 可以封装到组合式函数中,提高代码复用性

掌握生命周期钩子的使用方法和最佳实践,是编写可靠、高效Vue组件的关键。通过合理使用生命周期钩子,可以在组件的不同阶段执行特定的逻辑,管理资源,处理错误,以及优化性能。

11. 扩展阅读

通过本集的学习,我们深入理解了Vue 3组合式API中生命周期钩子的使用方法和最佳实践。在下一集中,我们将学习provide与inject依赖注入,这是组合式API中组件间通信的重要方式。

« 上一篇 setup函数深度解析 下一篇 » provide与inject依赖注入