Skip to content

在 Vue 3 中,组件实例的生命周期是其从创建到销毁的完整旅程。理解这一过程,以及组件实例内部用于跟踪状态的属性(如 _isMounted_isDestroyed),是深入掌握 Vue 响应式和渲染机制的关键。


一、组件生命周期的宏观流程

一个组件实例的生命周期可以分为三个主要阶段:

  1. 创建(Creation):实例化、设置响应式、编译模板。
  2. 挂载与更新(Mounting & Update):插入 DOM、响应数据变化。
  3. 卸载(Unmounting):从 DOM 移除、清理资源。

Vue 3 的组合式 API 主要通过 setup 和生命周期钩子来介入这一过程。


二、生命周期钩子函数

Vue 提供了一系列生命周期钩子,允许你在特定阶段执行代码。

1. 创建阶段

  • setup()

    • 组合式 API 的入口。
    • beforeCreatecreated 之间执行。
    • 返回 setup 中定义的响应式状态和方法。
  • beforeCreate(选项式):

    • 实例初始化后,数据观测 (data observation) 和事件/watcher 配置之前被调用。
  • created

    • 实例创建完成。
    • 数据、计算属性、方法、watch/event 回调都已设置。
    • 可以访问响应式数据
    • 但尚未挂载到 DOM$elundefined

2. 挂载阶段

  • beforeMount

    • 挂载开始前被调用。
    • render 函数首次被调用。
    • 虚拟 DOM 已创建,但尚未生成真实 DOM
  • mounted

    • 组件已挂载到 DOM。
    • el 属性现在可用。
    • 可以安全地访问和操作 DOM
    • 适合发起网络请求、设置定时器、集成第三方库。

3. 更新阶段

  • beforeUpdate

    • 响应式数据变化,导致重新渲染之前调用。
    • 虚拟 DOM 即将重新渲染。
    • DOM 仍是旧的状态
  • updated

    • 组件因响应式数据变化而重新渲染后调用。
    • DOM 已更新
    • 注意:避免在此钩子中修改状态,可能引发无限循环。

4. 卸载阶段

  • beforeUnmount(原 beforeDestroy):

    • 实例卸载之前调用。
    • 组件实例仍然完全正常。
    • 清理定时器、取消订阅、移除事件监听器
  • unmounted(原 destroyed):

    • 组件实例已从 DOM 中移除。
    • 所有指令都被解绑,所有事件监听器被移除,所有子组件实例被卸载。
    • 实例不可用

三、组件实例的内部状态

Vue 内部使用一系列以 _ 开头的属性来跟踪组件实例的当前状态。这些是内部属性,不应在应用代码中直接访问,但理解它们有助于理解生命周期。

1. _isMounted

  • 作用:标记组件是否已经挂载到 DOM。
  • 初始值false
  • 何时变为 true
    • mounted 钩子执行之后,由 Vue 内部设置为 true
  • 用途
    • 防止在 mounted 之前执行某些操作。
    • updated 钩子中,确保组件已经挂载。
    • 响应式系统在更新时检查此状态,避免对未挂载组件进行不必要的操作。
js
// 伪代码
callHook(instance, 'mounted');
instance._isMounted = true;

2. _isDestroyed(或 _isUnmounted

  • 作用:标记组件实例是否已被销毁(卸载)。
  • 初始值false
  • 何时变为 true
    • unmounted 钩子执行之后,由 Vue 内部设置为 true
  • 用途
    • 防止内存泄漏:在异步操作(如 setTimeout、API 响应)中,检查组件是否已销毁,避免在已卸载的组件上操作。
    • 避免无效更新:响应式系统在触发更新前会检查此标志,如果为 true,则跳过更新。
js
// 伪代码
callHook(instance, 'unmounted');
instance._isDestroyed = true;
// 之后清理引用

3. _isDeactivated(用于 <keep-alive>

  • 作用:标记组件是否被 <keep-alive> 缓存(失活)。
  • 初始值false
  • 何时变化
    • 组件被 <keep-alive> 包裹时,切换离开时变为 true,切换回来时变为 false
  • 相关钩子
    • onActivated:组件被激活时调用。
    • onDeactivated:组件被缓存(失活)时调用。

4. _vnode

  • 作用:指向当前组件的虚拟 DOM 节点(VNode)
  • 用途
    • 渲染过程中用于 patch 比较。
    • 在更新和卸载时引用当前 VNode。

5. _parent_children

  • 作用:维护组件树的父子关系。
  • 用途
    • provide/inject 依赖 _parent 链向上查找。
    • 卸载时递归卸载所有 _children

四、生命周期与内部状态的交互

以一个完整的生命周期为例:

js
// 1. 创建
const instance = createComponentInstance(vnode);
// instance._isMounted = false
// instance._isDestroyed = false

// 2. setup & created
setupComponent(instance);
callHook('created');

// 3. 挂载
callHook('beforeMount');
const subTree = instance.render(); // 生成虚拟 DOM
patch(null, subTree, container);   // 挂载到 DOM
callHook('mounted');
instance._isMounted = true;        // 标记为已挂载

// 4. 更新(数据变化)
if (instance._isMounted && !instance._isDestroyed) {
  callHook('beforeUpdate');
  const newSubTree = instance.render();
  patch(subTree, newSubTree, container);
  subTree = newSubTree;
  callHook('updated');
}

// 5. 卸载
callHook('beforeUnmount');
unmountChildren(instance.children); // 卸载子组件
remove(instance.vnode.el);          // 从 DOM 移除
callHook('unmounted');
instance._isDestroyed = true;       // 标记为已销毁
// 清理引用,等待垃圾回收

五、内部状态的实际应用(安全实践)

虽然你不应直接访问 _isMounted_isDestroyed,但理解它们能帮助你编写更安全的代码。

1. 避免在已销毁组件上操作

js
import { onMounted, onBeforeUnmount } from 'vue';

export default {
  setup() {
    let timer = null;

    onMounted(() => {
      timer = setInterval(() => {
        // ❌ 危险:组件可能已销毁
        // someAsyncOperation().then(() => this.updateData());
      }, 1000);
    });

    onBeforeUnmount(() => {
      clearInterval(timer);
      timer = null;
    });

    // ✅ 安全做法:使用 AbortController 或检查状态
    // 更推荐使用 Composition API 封装
  }
}

2. 使用 Composition API 封装

js
// utils.js
export function useInterval(callback, delay) {
  const timer = ref(null);

  onMounted(() => {
    if (delay !== null) {
      timer.value = setInterval(callback, delay);
    }
  });

  onBeforeUnmount(() => {
    if (timer.value) clearInterval(timer.value);
  });

  return timer;
}

六、总结

组件实例的生命周期是一个精心编排的过程,Vue 通过内部状态(如 _isMounted_isDestroyed)精确地控制其各个阶段:

  • _isMounted:确保 DOM 操作只在挂载后执行。
  • _isDestroyed:防止内存泄漏,确保资源被正确清理。
  • _isDeactivated:支持 <keep-alive> 的缓存机制。

这些内部状态是 Vue 实现高效、可靠渲染的核心。作为开发者,你应通过生命周期钩子来介入这一过程,并遵循最佳实践,确保应用的健壮性和性能。