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

显示模式

登录
ARCHIVE DOCUMENTVUE

Vue 3 生命周期完整指南

所属馆藏
Vue
文件格式
Markdown
原始路径
Vue/09-Vue 3 生命周期完整指南
本文目录11 个章节
  1. Vue 生命周期钩子有哪些
  2. 在 Options API 中使用生命周期钩子
  3. 在 Composition API 中使用生命周期钩子
  4. 将 Vue 2 生命周期代码更新到 Vue 3
  5. 深入了解每个生命周期钩子
  6. activated()、deactivated() 与 KeepAlive
  7. errorCaptured 与 onErrorCaptured
  8. Vue 3 开发调试钩子
  9. SSR:哪些钩子会运行
  10. 总结
  11. 官方参考

Vue 3 生命周期完整指南

Category(分类): Vue Status: 已核对

最近开源了一个 Vue 组件,还不够完善,欢迎大家一起完善,也希望大家能给个 star 支持一下,谢谢各位。

GitHub 地址:github.com/qq449245884/vue-okr-tree

Vue 2 和 Vue 3 的生命周期主线非常相似。Vue 3 仍完整支持 Options API,并没有要求 Composition API 替代它;但 Vue 2 的 beforeDestroydestroyed 在标准 Vue 3 中改名为 beforeUnmountunmounted,迁移时并非“完全不用改代码”。

本文主要内容:

  1. Vue 生命周期钩子有哪些
  2. 在 Options API 中使用生命周期钩子
  3. 在 Composition API 中使用生命周期钩子
  4. 从 Vue 2 更新到 Vue 3
  5. 创建、挂载、更新与卸载
  6. KeepAlive 的激活与停用
  7. 错误、调试与 SSR 钩子
  8. 副作用清理

Vue 生命周期钩子有哪些

注意:下图是旧版简化示意图。 图中把 setup 标在 beforeCreatecreated 附近只是为了对照创建阶段,不表示会分别调用两次。每个组件的 setup() 只调用一次,并且在所有 Options API 生命周期钩子之前执行。

旧版 Vue 组件生命周期简化示意(setup 每个组件只调用一次)

可以先按四个主要阶段理解组件:创建、挂载、更新、卸载;每个挂载、更新、卸载阶段都有前后钩子。但“完整”API 还包括 KeepAlive、错误捕获、开发调试和 SSR 数据预取。

阶段Options APIComposition APISSR
初始化beforeCreatecreated没有对应的 onXxxsetup() 是更早的初始化入口执行
挂载beforeMountmountedonBeforeMountonMounted不执行
更新beforeUpdateupdatedonBeforeUpdateonUpdated不执行
卸载beforeUnmountunmountedonBeforeUnmountonUnmounted不执行
缓存activateddeactivatedonActivatedonDeactivated不执行
错误errorCapturedonErrorCaptured视错误发生阶段而定
开发调试renderTrackedrenderTriggeredonRenderTrackedonRenderTriggered不执行
服务端预取serverPrefetchonServerPrefetch仅 SSR,等待返回的 Promise

在 Options API 中使用生命周期钩子

Options API 钩子直接写在组件选项中,无需导入:

<script>
export default {
  mounted() {
    console.log('mounted!')
  },
  updated() {
    console.log('updated!')
  }
}
</script>

在 Composition API 中使用生命周期钩子

组合式钩子从 vue 导入,并且必须在 setup() 同步执行期间注册。异步 setup() 应在第一个 await 前注册,否则可能无法关联当前组件实例。

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

onMounted(() => {
  console.log('mounted in Composition API!')
})

onUnmounted(() => {
  console.log('unmounted in Composition API!')
})
</script>

将 Vue 2 生命周期代码更新到 Vue 3

下面是迁移记忆表,不表示 setup() 与创建钩子时序完全相同:

  • Vue 2 beforeCreatecreated:Vue 3 Options API 仍保留;组合式逻辑放在 setup()
  • beforeMountonBeforeMount
  • mountedonMounted
  • beforeUpdateonBeforeUpdate
  • updatedonUpdated
  • Vue 2 beforeDestroy → Vue 3 beforeUnmount / onBeforeUnmount
  • Vue 2 destroyed → Vue 3 unmounted / onUnmounted
  • activatedonActivated
  • deactivatedonDeactivated
  • errorCapturedonErrorCaptured

setup() 实际在所有 Options API 钩子(包括 beforeCreate)之前运行,且其中没有组件 this

深入了解每个生命周期钩子

beforeCreate()——Options API

此时 props 已解析,但 datacomputedmethodswatch 等状态选项尚未完成,不应依赖 DOM:

export default {
  data() {
    return { val: 'hello' }
  },
  beforeCreate() {
    console.log(this.val) // undefined
  }
}

created()——Options API

此时响应式 data、computed、methods 和 watch 已就绪,但没有 DOM:

export default {
  data() {
    return { val: 'hello' }
  },
  created() {
    console.log(`Value of val is: ${this.val}`)
  }
}

旧文建议“一律在 created 请求 API”过于绝对:created 在 SSR 也会执行,而且服务端创建的定时器等副作用可能没有卸载钩子清理。数据获取应依据 SSR 框架或 serverPrefetch 设计;只属于浏览器的副作用放在 mounted

Composition API 的创建阶段

Composition API 没有 onBeforeCreateonCreated。初始化逻辑直接写在 setup()<script setup>

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

const val = ref('hello')
console.log(`Value of val is: ${val.value}`)
</script>

beforeMount() 与 onBeforeMount()

首次渲染前调用,组件 DOM 尚未创建。Vue 3 是 $el(单数),但此时不应依赖它;模板 ref 通常也是 null

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

const root = ref(null)

onBeforeMount(() => {
  console.log(root.value) // 通常是 null
})
</script>

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

mounted() 与 onMounted()

挂载后可以访问自身 DOM。它保证自身 DOM 已插入父容器,并等待同步子组件挂载;不保证异步组件、<Suspense> 内组件、图片、字体或网络资源已经完成,也不会在 SSR 中调用。

Options API 通过 this.$el 访问组件根 DOM:

export default {
  mounted() {
    console.log(this.$el)
  }
}

Composition API 则通常使用模板 ref:

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

const root = ref(null)

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

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

两种写法表达的是相同的挂载时机。等待某次状态变更后的 DOM,应使用 await nextTick();等待资源则使用资源自身的 Promise 或 load 事件。

beforeUpdate() 与 onBeforeUpdate()

响应式状态变化、DOM patch 前调用,可读取更新前 DOM。不要在其中无条件修改参与当前渲染的状态,否则可能递归更新。旧文用它统计编辑次数会导致循环,应该改用 watch

updated() 与 onUpdated()

组件 DOM 更新后调用。父组件的 updated 在子组件之后运行;它是任意组件更新的通知,不适合精确监听某一个值。官方明确警告:不要在 updated 中修改会触发自身渲染的状态

下面保留原“统计编辑次数”主题,但改为 watcher:

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

const val = ref(0)
const count = ref(0)

watch(val, () => {
  count.value++
})
</script>

<template>
  <p>{{ val }} | edited {{ count }} times</p>
  <button @click="val = Math.floor(Math.random() * 101)">
    Click to Change
  </button>
</template>

若要在特定状态变化后读取 DOM,可用 watch(source, callback, { flush: 'post' }) 或在赋值后 await nextTick(),而不是把业务都放进 updated

beforeUnmount() 与 onBeforeUnmount()

卸载前实例仍然可用,适合拆除依赖实例或 DOM 的插件。原来的 onBeforeUnmounted 是错误名称。

export default {
  mounted() {
    window.addEventListener('resize', this.someMethod)
  },
  beforeUnmount() {
    window.removeEventListener('resize', this.someMethod)
  },
  methods: {
    someMethod() {
      // 处理窗口变化
    }
  }
}
<script setup>
import { onMounted, onBeforeUnmount } from 'vue'

function handleResize() {
  // 处理窗口变化
}

onMounted(() => window.addEventListener('resize', handleResize))
onBeforeUnmount(() => window.removeEventListener('resize', handleResize))
</script>

unmounted() 与 onUnmounted()

卸载后,子组件已卸载,组件的 render effect,以及在 setup() 中同步创建并绑定组件的 computed、watch、watchEffect 会停止。但手动的定时器、DOM 监听器、WebSocket、第三方实例仍需显式清理:

Options API 和 Composition API 都可以在卸载后执行收尾逻辑。下面先看 Options API:

export default {
  mounted() {
    this.timer = window.setInterval(() => console.log('tick'), 1000)
  },
  unmounted() {
    window.clearInterval(this.timer)
  }
}

对应的 Composition API 写法:

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

let timer

onMounted(() => {
  timer = window.setInterval(() => console.log('tick'), 1000)
})

onUnmounted(() => {
  window.clearInterval(timer)
})
</script>

异步回调里创建的 watcher 不一定绑定组件,应保存其停止句柄。请求副作用可通过 watcher 回调第三参数 onCleanup 取消;Vue 3.5+ 也有必须在同步阶段调用的 onWatcherCleanup()

activated()、deactivated() 与 KeepAlive

<KeepAlive> 切换离开的缓存实例会停用而非卸载:

<template>
  <button @click="tabName = Tab1">Tab 1</button>
  <button @click="tabName = Tab2">Tab 2</button>
  <KeepAlive>
    <component :is="tabName" />
  </KeepAlive>
</template>

<script setup>
import { shallowRef } from 'vue'
import Tab1 from './Tab1.vue'
import Tab2 from './Tab2.vue'

const tabName = shallowRef(Tab1)
</script>

Tab1.vue 中放一个输入框,就能直观看到切换到 Tab 2 再返回时,输入内容仍被缓存:

<template>
  <div>
    <h2>Tab 1</h2>
    <input type="text" placeholder="this content will persist!">
  </div>
</template>

<script setup>
import { onActivated, onDeactivated } from 'vue'

onActivated(() => console.log('Tab 1 Activated'))
onDeactivated(() => console.log('Tab 1 Deactivated'))
</script>

缓存组件内部也可以用这两个钩子管理轮询、订阅等资源:

<script setup>
import { onActivated, onDeactivated } from 'vue'

onActivated(() => {
  // 首次挂载及从缓存重新插入时调用:恢复轮询/订阅
})

onDeactivated(() => {
  // 移入缓存以及最终卸载时调用:暂停资源
})
</script>

v-show 不会触发这两个钩子。资源必须暂停时,不能只依靠视觉隐藏。

KeepAlive 切换后保留 Tab1 输入框内容

errorCaptured 与 onErrorCaptured

它们捕获后代组件在渲染、事件处理、生命周期、setup()、watcher 等阶段抛出的错误,参数为错误、暴露的组件实例和来源信息。返回 false 可阻止错误继续传播,但应避免在错误处理本身再次渲染同一故障内容。生产环境还可以配置 app.config.errorHandler 统一上报。

export default {
  errorCaptured(error, instance, info) {
    console.error(error, info)
    return false
  }
}

Vue 3 开发调试钩子

renderTracked / onRenderTracked 在渲染 effect 跟踪依赖时调用;renderTriggered / onRenderTriggered 在依赖变化触发渲染时调用。二者只在开发模式生效,不在 SSR 调用,也不应承载业务逻辑:

下面保留原文的购物车交互例子。首次渲染读取 cart 时会调用 renderTracked;点击按钮写入 cart 时会调用 renderTriggered

<div id="app">
  <button @click="addToCart">Add to cart</button>
  <p>Cart({{ cart }})</p>
</div>
const app = Vue.createApp({
  data() {
    return { cart: 0 }
  },
  renderTracked({ key, target, type }) {
    console.log('tracked', { key, target, type })
    // 首次渲染会记录对 cart 的 get 操作
  },
  renderTriggered({ key, target, type }) {
    console.log('triggered', { key, target, type })
    // 点击按钮后会记录对 cart 的 set 操作
  },
  methods: {
    addToCart() {
      this.cart += 1
    }
  }
})

app.mount('#app')

调试事件对象的 target 是发生响应式操作的对象,key 是被访问或修改的键,type 则描述 getsetadddelete 等操作类型。这样既能看到“渲染跟踪了什么”,也能看到“什么变化触发了重渲染”。

SSR:哪些钩子会运行

SSR 没有动态挂载与更新,因此 mountedupdated、卸载、KeepAlive 和渲染调试钩子不会在服务端执行;setup()beforeCreatecreated 会执行。不要在这些通用初始化位置无条件访问 windowdocument,也不要启动只能由客户端卸载钩子清理的定时器。

服务端预取可以使用 serverPrefetch / onServerPrefetch,或 Nuxt 等框架的数据 API:

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

const article = ref(null)

onServerPrefetch(async () => {
  article.value = await fetch('https://example.com/api/article').then(r => r.json())
})
</script>

总结

无论选择 Options API 还是 Composition API,都应按副作用所依赖的资源选择钩子,而不是为了“使用新 API”机械替换。DOM 工作放在挂载后,精确状态变化用 watch,手动资源在卸载时清理,缓存组件用激活/停用管理,SSR 数据使用专门入口。

官方参考

~完,我是刷碗智,我要去刷碗了,骨的白。

作者:王大冶

链接:https://juejin.cn/post/6945606524987244558 来源:稀土掘金。著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

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

按分类浏览

10 COLLECTIONS