技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTVUE

Vue 3 生命周期详解

所属馆藏
Vue
文件格式
Markdown
原始路径
Vue/31-Vue3生命周期详解
本文目录12 个章节
  1. 一、什么是生命周期?
  2. 二、初始化阶段
  3. 三、挂载阶段
  4. 四、响应式更新阶段
  5. 五、卸载阶段
  6. 六、错误捕获
  7. 七、KeepAlive 生命周期
  8. 八、SSR、Suspense 与异步内容
  9. 九、源码与调度器简析
  10. 十、总结
  11. 官方参考
  12. 原文出处

Vue 3 生命周期详解

原文标注的版本是 Vue 3.0.2,下面保留其“用示例和源码理解生命周期”的结构,并按 Vue 3.5.x 重新校正 API、调度器、错误处理、SSR、KeepAlive 和 Suspense。Vue 3.5.39 是本文示例的主要基线。

一、什么是生命周期?

组件从创建、挂载、更新到卸载,会在不同阶段调用不同的生命周期 hook。生命周期 hook 的作用不是让我们手动控制整个渲染过程,而是让组件在合适的阶段注册副作用、读取 DOM、清理资源或观察调试信息。

Vue 3 的生命周期可以分成以下几组:

场景Options APIComposition API
创建beforeCreatecreatedsetup() 承担创建阶段逻辑
挂载beforeMountmountedonBeforeMountonMounted
更新beforeUpdateupdatedonBeforeUpdateonUpdated
卸载beforeUnmountunmountedonBeforeUnmountonUnmounted
KeepAliveactivateddeactivatedonActivatedonDeactivated
错误errorCapturedonErrorCaptured
调试renderTrackedrenderTriggeredonRenderTrackedonRenderTriggered
SSRserverPrefetchonServerPrefetch

Composition API 没有 onBeforeCreateonCreated,通常把对应逻辑直接写在 setup()<script setup> 的同步部分。

Vue 3 生命周期示意图(本地化)

二、初始化阶段

Options API 的 beforeCreatecreated

export default {
  data() {
    return {
      value: 'hello',
    }
  },
  beforeCreate() {
    // data、computed、methods 等选项还没有完成实例代理
    console.log('beforeCreate', this.value)
  },
  created() {
    // data、computed、methods 已经可以访问
    // 但 DOM 尚未挂载
    console.log('created', this.value)
  },
}

beforeCreate 适合了解实例创建顺序,但现代业务通常不需要在这里做很多工作。需要读写响应式状态、发起客户端请求时,可以在 created 或 Composition API 的 setup() 中处理;需要访问 DOM 则应等待 mounted

Composition API 的 setup()

<script setup>
import { ref } from 'vue'

const value = ref('hello')
console.log(value.value)
</script>

在同时使用 Options API 和 Composition API 的组件中,setup() 会在 Options API 的 beforeCreate 之前执行。因此,“setup() 等价于 created”只是方便迁移的概念映射,并不是严格的一对一替换。

生命周期注册还必须同步进行:

<script setup>
import { onMounted } from 'vue'

// 正确:同步注册
onMounted(() => {})

// 不推荐:定时器执行时已经没有当前组件实例
setTimeout(() => {
  // onMounted(() => {})
}, 100)
</script>

如果使用 async setup(),应在第一个 await 之前注册生命周期;<script setup> 对顶层 await 有额外的实例上下文恢复支持。

三、挂载阶段

beforeMount / onBeforeMount

这个 hook 在组件即将开始首次 DOM patch 前调用。此时组件的响应式状态已经建立,但最终 DOM 还没有完成挂载。不要在这里假设所有子组件或异步内容都已经存在。

export default {
  beforeMount() {
    console.log('beforeMount', this.$el)
  },
}

Composition API 中,如果需要访问元素,应使用模板 ref,但在 onBeforeMount 中 ref 通常仍然是 null

<script setup>
import { onBeforeMount, ref } from 'vue'

const root = ref(null)

onBeforeMount(() => {
  console.log(root.value) // 通常仍未挂载
})
</script>

<template>
  <div ref="root">Hello Vue</div>
</template>

mounted / onMounted

mounted 表示组件自己的 DOM 已创建并插入,且同步子组件通常也已经挂载。它不保证异步组件、Suspense 分支或所有后续异步数据已经完成。

<script setup>
import { onMounted, ref } from 'vue'

const root = ref(null)

onMounted(() => {
  console.log(root.value)
})
</script>

<template>
  <div ref="root">Hello Vue</div>
</template>

常见用途包括:

  • 初始化需要 DOM 的第三方库;
  • 添加 window / document 事件监听器;
  • 读取元素尺寸或焦点;
  • 启动只应在浏览器中运行的副作用。

如果需要在服务端获取数据,不要依赖 onMounted,应使用框架的数据获取能力或 onServerPrefetch()

四、响应式更新阶段

renderTracked:依赖被 render effect 读取

renderTracked 是开发环境调试 hook,表示当前组件的 render effect 跟踪到了响应式依赖。它不是“页面上任意取值都会触发”,也不适合写业务逻辑。

Options API:

export default {
  renderTracked(event) {
    console.log('render tracked:', event)
  },
}

Composition API:

<script setup>
import { onRenderTracked } from 'vue'

onRenderTracked(event => {
  console.log('render tracked:', {
    type: event.type,
    key: event.key,
    target: event.target,
  })
})
</script>

常见 event.type 包括 gethasiterate。这些调试 hook 只在开发构建中有意义,SSR 不执行。

renderTriggered:依赖触发 render effect

renderTriggered 表示已经被当前 render effect 依赖的响应式操作触发了重新渲染。

<script setup>
import { onRenderTriggered, ref } from 'vue'

const count = ref(0)

onRenderTriggered(event => {
  console.log('render triggered:', {
    type: event.type,
    key: event.key,
    oldValue: event.oldValue,
    newValue: event.newValue,
  })
})
</script>

<template>
  <p>{{ count }}</p>
  <button @click="count++">加 1</button>
</template>

常见触发类型包括 setadddeleteclear。如果某个响应式值没有被当前组件模板读取,修改它不会触发该组件的 renderTriggered

beforeUpdate / onBeforeUpdate

在组件因响应式状态变化而重新渲染时,beforeUpdate 在本次 DOM patch 前调用:

export default {
  data() {
    return {
      value: 0,
    }
  },
  beforeUpdate() {
    console.log('DOM update is about to start:', this.value)
  },
}

这里可以读取更新前的 DOM 状态,但不建议在其中无条件修改响应式状态,否则可能造成额外更新或循环。

updated / onUpdated

updated 在本次 DOM patch 完成后调用。Vue 会批量合并同一轮同步修改,因此多次同步赋值不一定触发多次 updated

<script setup>
import { nextTick, onUpdated, ref } from 'vue'

const value = ref(0)

onUpdated(() => {
  console.log('DOM updated')
})

async function update() {
  value.value++
  await nextTick()
  // 等待本轮 DOM 更新完成
}
</script>

不要在 updated 中无条件修改组件依赖的状态;如果只是需要等待某次 DOM 更新,优先在修改状态的函数中使用 nextTick()

五、卸载阶段

beforeUnmount / onBeforeUnmount

组件开始卸载前调用,此时实例仍然可用,适合清理事件监听器、定时器、WebSocket、观察器和第三方实例:

<script setup>
import { onMounted, onBeforeUnmount } from 'vue'

function handleResize() {
  console.log(window.innerWidth)
}

onMounted(() => {
  window.addEventListener('resize', handleResize)
})

onBeforeUnmount(() => {
  window.removeEventListener('resize', handleResize)
})
</script>

unmounted / onUnmounted

组件卸载完成后调用:

<script setup>
import { onUnmounted } from 'vue'

onUnmounted(() => {
  console.log('component unmounted')
})
</script>

手动注册的副作用应在 onBeforeUnmountonUnmounted 清理。普通 ref、computed 和组件内部 watcher 通常会随组件作用域一起停止,但异步回调中创建的 watcher 可能不再自动绑定到组件,需要手动停止。

六、错误捕获

errorCaptured 的传播方向

errorCaptured 捕获的是后代组件产生的错误,错误会沿父链由近及远传播,类似事件冒泡:

错误来源组件
  -> 最近的父组件 errorCaptured
  -> 更外层祖先 errorCaptured
  -> app.config.errorHandler

错误来源组件自身的 errorCaptured 不会捕获自己的错误。捕获 hook 返回 false 后,会阻止错误继续传递给后续祖先和全局 handler;默认情况下,即使某个 hook 已记录错误,仍可能继续交给 app.config.errorHandler

可运行的 Composition API 示例

import { createApp, defineComponent } from 'vue'

const Child = defineComponent({
  setup() {
    throw new Error('child setup failed')
  },
  template: '<div>Child</div>',
})

const Parent = defineComponent({
  components: { Child },
  errorCaptured(error, instance, info) {
    console.error('parent captured:', error, info)
    return false
  },
  template: '<Child />',
})

const app = createApp({
  components: { Parent },
  template: '<Parent />',
})

app.config.errorHandler = (error, instance, info) => {
  console.error('global handler:', error, info)
}

app.mount('#app')

由于 Parent.errorCaptured() 返回了 false,child 的错误不会继续传给全局 handler。这个结果依赖错误来源、祖先结构和返回值,不应把某一组控制台输出当成所有场景的固定顺序。

常见错误来源

Vue 的错误处理覆盖 setup()、render、生命周期、watcher、事件处理器、指令和 transition 等。errorCaptured 适合记录和展示降级 UI,不应把所有错误都静默吞掉。

七、KeepAlive 生命周期

<KeepAlive> 会缓存动态组件实例。切换缓存组件时,组件一般不会被卸载,而是触发:

  • activated / onActivated:组件被激活;首次挂载时也会调用。
  • deactivated / onDeactivated:组件被移出活动树;最终卸载时也会调用。
<script setup>
import { onActivated, onDeactivated } from 'vue'

onActivated(() => {
  console.log('tab activated')
})

onDeactivated(() => {
  console.log('tab deactivated')
})
</script>
<template>
  <KeepAlive>
    <component :is="currentTab" />
  </KeepAlive>
</template>

如果只是切换 KeepAlive 的 tab,不应期待每次都重新执行 mounted/unmounted。需要保存滚动位置、暂停轮询或恢复第三方资源时,可以使用 activated/deactivated。

八、SSR、Suspense 与异步内容

SSR

SSR 中通常会执行 setup() 和创建阶段逻辑,但不会执行依赖浏览器 DOM 的 hook:

会执行:setup、created 及可能的 serverPrefetch
不会执行:mounted、updated、unmounted、activated 等 DOM/客户端生命周期

使用 onServerPrefetch()

<script setup>
import { onServerPrefetch, ref } from 'vue'

const user = ref(null)

onServerPrefetch(async () => {
  user.value = await fetchUser()
})
</script>

SSR renderer 会等待 onServerPrefetch() 返回的 Promise。普通 onMounted(async () => {}) 不会在服务端执行,也不会让 SSR 等待。

Suspense

Suspense 可以等待异步 setup 和异步组件:

<template>
  <Suspense>
    <AsyncPanel />

    <template #fallback>
      正在加载……
    </template>
  </Suspense>
</template>

Suspense pending 时,异步分支的 post-render effects 可能先缓存在 boundary 中。因此父组件 mounted 不表示 Suspense 内的异步后代已经 mounted。Suspense 当前仍属于需要关注版本和边界的高级能力。

九、源码与调度器简析

原文的源码片段来自 Vue 3.0.2,以下结论仍然有效,但字段和队列已更新:

  1. apiLifecycle.tscreateHook/injectHook 把 callback 包装后保存到组件实例的 hook 数组。
  2. renderer 在组件首次 render effect 中同步调用 beforeMount,在 patch 后排队 mounted。
  3. 更新时同步调用 beforeUpdate,在 patch 后排队 updated。
  4. scheduler.ts 用主队列和 post 队列批量处理任务;Vue 3.5 用 QUEUEDPREALLOW_RECURSEDISPOSED flags 管理去重和失效。
  5. watcher 默认 flush: 'pre',post watcher 走 post-render effect,sync watcher 直接运行。
  6. nextTick() 等待当前 flush Promise,而不是等待所有任意异步请求。

Vue 3.5 不再使用原文中独立的 pendingPreFlushCbs 作为默认 pre watcher 主模型,也不能把 queuePreFlushCb 当成当前公共 API。精确实现请阅读:

十、总结

  • setup() 承担 Composition API 的创建阶段逻辑。
  • onMountedonUpdated 等 hook 必须在当前组件实例存在时同步注册。
  • before 类 hook 通常在对应渲染/卸载流程中同步执行,mounted/updated/unmounted 通常通过 post-render 调度。
  • renderTracked/renderTriggered 只用于开发环境调试 render effect。
  • 错误从来源组件沿父链向上捕获,return false 才会停止后续传播。
  • KeepAlive 使用 activated/deactivated,Suspense 和异步组件会改变普通父子生命周期的简单顺序。
  • SSR 使用 onServerPrefetch 获取服务端数据,普通 DOM 生命周期不在 SSR 调用。

官方参考

原文出处

作者:摆烂学前端

原文链接:https://juejin.cn/post/7020017329815683085

来源:稀土掘金。著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS