Vue3 生命周期与Hooks

一、Vue3 生命周期全览

组件的生命周期是由一系列的钩子函数组成的,这些钩子在组件的不同创建和销毁阶段被调用。这些生命周期钩子提供了在特定时刻添加自定义行为的机会,例如在组件挂载到DOM后或数据更新前后执行代码。

Options API 与 Composition API 对照表

Options APIComposition API触发时机
beforeCreate无需映射在 setup() 之前执行
created无需映射在 setup() 之前执行
beforeMountonBeforeMount挂载开始之前
mountedonMounted挂载完成后
beforeUpdateonBeforeUpdate响应式数据变化导致重新渲染前
updatedonUpdated重新渲染完成后
beforeUnmountonBeforeUnmount组件卸载前
unmountedonUnmounted组件卸载后
errorCapturedonErrorCaptured捕获子孙组件错误时

二、Composition API 生命周期使用

基础示例

<script setup>
import { 
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted 
} from 'vue'

// 组件挂载阶段
onBeforeMount(() => {
  console.log('挂载前 - DOM 尚未创建')
})

onMounted(() => {
  console.log('挂载完成 - 可访问 DOM')
  window.addEventListener('resize', handleResize)
})

// 组件更新阶段
onBeforeUpdate(() => {
  console.log('数据变化,即将重新渲染')
})

onUpdated(() => {
  console.log('重新渲染完成')
})

// 组件卸载阶段
onBeforeUnmount(() => {
  console.log('组件即将卸载')
})

onUnmounted(() => {
  console.log('组件已卸载')
  window.removeEventListener('resize', handleResize)
})

function handleResize() {
  console.log('窗口大小变化')
}
</script>

三、关于 Hooks

在Vue3中,Hooks是基于Composition API实现的一组可复用的函数。这些函数可以“钩入”Vue组件的生命周期,让我们能够在组件的不同生命周期阶段执行特定的逻辑。

1. Hooks的实现原理

在Vue3中,Hooks通过setup函数来使用。setup函数是Vue3组件中的一个新的生命周期函数,它在组件实例被创建之前调用,并且接收两个参数:propscontext。在setup函数中,我们可以定义和返回组件中需要使用的响应式数据、方法、计算属性等,而这些都可以通过Hooks来实现。

2 .Hooks的使用场景

  1. 逻辑复用:当多个组件需要共享相同的逻辑时,我们可以将这些逻辑封装成一个Hook,然后在需要的组件中导入并使用它。这样可以避免代码重复,提高代码的复用性。

  2. 逻辑拆分:对于复杂的组件,我们可以使用Hooks将组件的逻辑拆分成多个独立的函数,每个函数负责处理一部分逻辑。这样可以使组件的代码更加清晰、易于维护。

  3. 副作用管理Hooks中的函数可以访问组件的响应式数据,并且可以在组件的生命周期中执行副作用操作(如定时器、事件监听等)。通过使用Hooks,我们可以更好地管理这些副作用操作,确保它们在组件卸载时得到正确的清理。


四、自定义 Hook 开发指南

1. 鼠标位置跟踪 Hook

// hooks/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 }
}

// 组件中使用
<script setup>
import { useMousePosition } from './hooks/useMousePosition'

const { x, y } = useMousePosition()
</script>

<template>
  鼠标位置:{{ x }}, {{ y }}
</template>

2. 异步数据请求 Hook

// hooks/useFetch.js
import { ref, onBeforeUnmount } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)
  let abortController = new AbortController()

  const fetchData = async () => {
    try {
      loading.value = true
      const response = await fetch(url, { 
        signal: abortController.signal 
      })
      data.value = await response.json()
    } catch (err) {
      error.value = err
    } finally {
      loading.value = false
    }
  }

  onBeforeUnmount(() => {
    abortController.abort()
  })

  return {
    data,
    error,
    loading,
    fetchData
  }
}

// 组件中使用
<script setup>
import { useFetch } from './hooks/useFetch'

const { data, loading, error, fetchData } = useFetch('/api/data')

onMounted(() => {
  fetchData()
})
</script>

五、最佳实践与常见问题

1. 生命周期使用原则

  • 在 onMounted 中访问 DOM/初始化第三方库

  • 在 onUnmounted 中清理副作用(定时器、事件监听)

  • 避免在 onUpdated 中修改状态(可能导致无限循环)

2. 自定义 Hook 规范

  • 命名以 use 开头(如 useDarkMode

  • 单一职责原则(一个 Hook 只解决一个问题)

  • 返回响应式数据时使用 toRefs

    return toRefs(reactive({ x, y }))

3. 常见问题解决
Q:如何在 setup 中访问 this?

  • 不需要也不应该访问 this,所有上下文通过 Composition API 获取

Q:多个 Hook 之间如何共享状态?

// 共享状态 Hook
export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const increment = () => count.value++
  
  return {
    count,
    increment
  }
}

// 组件 A
const { count: countA } = useCounter()

// 组件 B
const { count: countB } = useCounter(10)

Q:如何处理异步操作的竞态条件?

// 在 Hook 中添加请求标识
let requestId = 0

const fetchData = async () => {
  const currentId = ++requestId
  const res = await fetch(url)
  if (currentId === requestId) {
    // 处理有效响应
  }
}

六、高级应用场景

1. 组合多个 Hook

// 组件逻辑
const { user } = useUser()
const { posts, loading } = usePosts(user.value.id)
const { width } = useWindowSize()

2. 带参数的动态 Hook

// 根据路由参数变化的 Hook
export function useRouteData() {
  const route = useRoute()
  const id = computed(() => route.params.id)
  
  const { data, fetch } = useFetch(`/api/${id.value}`)
  
  watch(id, fetch, { immediate: true })
  
  return { data }
}

3. 生命周期可视化调试

// 开发环境专用 Hook
export function useLifecycleLogger(name) {
  if (process.env.NODE_ENV === 'development') {
    onBeforeMount(() => console.log(`${name} - beforeMount`))
    onMounted(() => console.log(`${name} - mounted`))
    // 其他生命周期同理...
  }
}

七、TypeScript 增强示例

1. 类型化生命周期

interface Position {
  x: number
  y: number
}

export function useMousePosition(): {
  x: Ref<number>
  y: Ref<number>
} {
  // 实现同上
}

2. 泛型请求 Hook

export function useFetch<T>(url: string) {
  const data = ref<T | null>(null)
  // 其他逻辑...
  return { data }
}

// 使用示例
interface User {
  id: number
  name: string
}

const { data } = useFetch<User[]>('/api/users')

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值