vue3源码解析:生命周期

本文,我们将核心流程再过一遍,重点关注所有生命周期钩子函数的执行时机,以及它们在不同场景下的执行顺序。

1. 生命周期钩子函数的执行时机

Vue3 的生命周期主要分为四个阶段,每个阶段都有对应的生命周期钩子:

初始化阶段

  • setup: 组件实例创建之前执行
  • beforeCreate: 实例初始化之前 (在 Composition API 中等同于 setup 开始)
  • created: 实例创建完成后 (在 Composition API 中等同于 setup 结束)

挂载阶段

  • beforeMount: DOM 挂载之前
  • mounted: DOM 挂载完成后

更新阶段

  • beforeUpdate: 数据更新时,DOM 更新之前
  • updated: DOM 更新完成后

卸载阶段

  • beforeUnmount: 组件卸载之前
  • unmounted: 组件卸载完成后

2. 组件初始化阶段

组件的生命周期从初始化开始。在这个阶段,Vue 会创建组件实例,设置响应式系统,并执行初始化相关的钩子函数。让我们详细看看这个阶段的实现:

2.1 setup

setup 是 Composition API 的入口,也是整个组件生命周期中最早执行的钩子。它在组件实例创建之前执行,这使得我们可以在这里进行一些预处理工作,比如创建响应式数据、注册生命周期钩子等。具体实现如下:

js 复制代码
// 调用链:
// createApp -> mount -> render -> patch -> processComponent ->
// 在 setupComponent 函数中
if (setup) {
  const setupContext = (instance.setupContext =
    setup.length > 1 ? createSetupContext(instance) : null);

  setCurrentInstance(instance);
  pauseTracking();
  const setupResult = callWithErrorHandling(
    setup,
    instance,
    ErrorCodes.SETUP_FUNCTION,
    [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
  );
  resetTracking();
  unsetCurrentInstance();
}

实际应用示例:

js 复制代码
// 在 setup 中初始化数据和方法
export default {
  setup() {
    // 1. 创建响应式数据
    const count = ref(0);
    const userInfo = reactive({
      name: "",
      age: 0,
    });

    // 2. 定义方法
    const increment = () => count.value++;

    // 3. 注册其他生命周期钩子
    onMounted(() => {
      // 组件挂载后的逻辑
    });

    return { count, userInfo, increment };
  },
};

2.2 beforeCreate & created

在 Vue3 的 Composition API 中,这两个钩子实际上等同于 setup 函数:

  • beforeCreate: 在 setup 开始时
  • created: 在 setup 结束时

如果使用 Options API,这两个钩子会在组件实例初始化时执行:

js 复制代码
// 调用链:
// createApp -> mount -> render -> patch -> processComponent ->
// mountComponent -> setupComponent -> setupStatefulComponent -> applyOptions

// componentOptions.ts
function applyOptions(instance: ComponentInternalInstance) {
  const options = resolveMergedOptions(instance);
  const publicThis = instance.proxy! as any;
  const ctx = instance.ctx;

  // 1. 处理生命周期钩子
  if (options.beforeCreate) {
    callHook(options.beforeCreate, instance, LifecycleHooks.BEFORE_CREATE);
  }

  // ... 其他选项的处理

  if (options.created) {
    callHook(options.created, instance, LifecycleHooks.CREATED);
  }
}

// 生命周期钩子的枚举定义
export const enum LifecycleHooks {
  BEFORE_CREATE = "bc",
  CREATED = "c",
  BEFORE_MOUNT = "bm",
  MOUNTED = "m",
  BEFORE_UPDATE = "bu",
  UPDATED = "u",
  BEFORE_UNMOUNT = "bum",
  UNMOUNTED = "um",
  DEACTIVATED = "da",
  ACTIVATED = "a",
  RENDER_TRIGGERED = "rtg",
  RENDER_TRACKED = "rtc",
  ERROR_CAPTURED = "ec",
}

// 钩子函数的调用实现
function callHook(
  hook: Function,
  instance: ComponentInternalInstance,
  type: LifecycleHooks
) {
  callWithAsyncErrorHandling(hook.bind(instance.proxy), instance, type);
}

钩子函数的执行过程:

  1. beforeCreate:

    • 在 applyOptions 开始时执行
    • 此时组件实例已创建,但还未进行其他选项的处理
    • 不能访问 data、computed 等选项
  2. created:

    • 在其他选项处理完成后执行
    • 此时已完成响应式数据、计算属性等的初始化
    • 可以访问组件的所有选项

在 Composition API 中,这两个钩子的功能被 setup 函数取代,因为:

  1. setup 函数提供了更灵活的组合式 API
  2. 可以更好地组织和复用逻辑
  3. 提供了更好的类型推导

实际应用示例:

js 复制代码
export default {
  // Options API
  beforeCreate() {
    // 这里不能访问 data、computed 等
    console.log("组件实例还未完全创建");
  },
  created() {
    // 1. 可以访问响应式数据
    console.log(this.someData);

    // 2. 发起初始化数据请求
    this.fetchInitialData();

    // 3. 添加全局事件监听
    window.addEventListener("resize", this.handleResize);
  },
};

3. 挂载和更新阶段

当组件实例创建完成后,Vue 会进入挂载和更新阶段。这两个阶段的生命周期钩子都在 setupRenderEffect 函数中实现:

3.1 beforeMount

在 setupRenderEffect 函数中,组件挂载前会执行:

js 复制代码
// createAppAPI -> mount -> render -> patch -> processComponent ->
// mountComponent -> setupComponent -> setupRenderEffect

const setupRenderEffect = (
  instance,
  initialVNode,
  container,
  anchor,
  parentSuspense,
  namespace,
  optimized
) => {
  const componentUpdateFn = () => {
    if (!instance.isMounted) {
      // 挂载阶段
      // 1. 执行 beforeMount 钩子
      const { bm } = instance;
      if (bm) {
        invokeArrayFns(bm);
      }

      // 2. 渲染组件
      const subTree = (instance.subTree = renderComponentRoot(instance));

      // 3. 挂载子树
      patch(
        null,
        subTree,
        container,
        anchor,
        instance,
        parentSuspense,
        namespace
      );
      instance.isMounted = true;

      // 4. 执行 mounted 钩子(异步)
      const { m } = instance;
      if (m) {
        queuePostRenderEffect(m, parentSuspense);
      }
    } else {
      // 更新阶段
      // 1. 执行 beforeUpdate 钩子
      const { bu } = instance;
      if (bu) {
        invokeArrayFns(bu);
      }

      // 2. 更新组件
      const nextTree = renderComponentRoot(instance);
      const prevTree = instance.subTree;
      instance.subTree = nextTree;

      // 3. 更新 DOM
      patch(
        prevTree,
        nextTree,
        container,
        anchor,
        instance,
        parentSuspense,
        namespace
      );

      // 4. 执行 updated 钩子(异步)
      const { u } = instance;
      if (u) {
        queuePostRenderEffect(u, parentSuspense);
      }
    }
  };

  // 创建渲染副作用
  instance.update = effect(componentUpdateFn, {
    scheduler: () => queueJob(instance.update),
  });
};

特点:

  1. 同步钩子(beforeMount/beforeUpdate)在 DOM 操作前执行,可以访问更新前的状态
  2. 异步钩子(mounted/updated)通过 queuePostRenderEffect 调度,确保在 DOM 更新后执行
  3. 整个过程由 effect 包装,响应式数据变化时自动触发更新流程

实际应用示例:

js 复制代码
export default {
  setup() {
    onBeforeMount(() => {
      // 1. DOM 渲染前的准备工作
      initializeThirdPartyLib();
    });

    onMounted(() => {
      // 1. 访问或操作 DOM
      const element = document.getElementById("my-element");

      // 2. 初始化第三方库
      new ThirdPartyChart(element);

      // 3. 发起数据请求
      fetchData();
    });

    onBeforeUpdate(() => {
      // 1. 更新前保存一些状态
      saveScrollPosition();
    });

    onUpdated(() => {
      // 1. DOM 更新后恢复状态
      restoreScrollPosition();

      // 2. 更新第三方库的视图
      updateChartView();
    });
  },
};

4. 卸载阶段

当组件被卸载时(比如通过 v-if 移除组件或手动调用 unmount),Vue 会执行一系列清理工作。让我们看看卸载阶段的生命周期钩子实现:

js 复制代码
// unmount 调用链:
// 1. 组件被移除 -> render(null, container)
// 2. patch(prevVNode, null, ...)
// 3. unmount(prevVNode, ...)
// 4. unmountComponent(...)

const unmountComponent = (
  instance: ComponentInternalInstance,
  parentSuspense: SuspenseBoundary | null,
  doRemove?: boolean
) => {
  const { bum, scope, update, subTree, um } = instance;

  // 1. 执行 beforeUnmount 钩子
  if (bum) {
    invokeArrayFns(bum);
  }

  // 2. 停止响应式副作用
  scope.stop();
  if (update) {
    update.active = false;
  }

  // 3. 卸载子树
  unmount(subTree, instance, parentSuspense, doRemove);

  // 4. 执行 unmounted 钩子(异步)
  if (um) {
    queuePostRenderEffect(um, parentSuspense);
  }
};

特点:

  1. 卸载过程是自上而下的,先处理父组件,再处理子组件
  2. beforeUnmount 是同步执行的,确保在 DOM 移除前有机会进行清理
  3. unmounted 是异步执行的,确保所有子组件都完成卸载
  4. 完整的清理机制确保不会产生内存泄漏

实际应用示例:

js 复制代码
export default {
  setup() {
    // 1. 创建需要清理的资源
    const timer = setInterval(() => {
      // 定时任务
    }, 1000);

    const unsubscribe = store.subscribe(() => {
      // 状态订阅
    });

    onBeforeUnmount(() => {
      // 1. 清除定时器
      clearInterval(timer);

      // 2. 取消状态订阅
      unsubscribe();

      // 3. 移除事件监听
      window.removeEventListener("resize", handleResize);
    });

    onUnmounted(() => {
      // 1. 销毁第三方库实例
      chart.destroy();

      // 2. 清理缓存数据
      localStorage.removeItem("tempData");
    });
  },
};

4. 总结

Vue3 的生命周期钩子执行顺序如下:

初始化阶段

  1. setup (最早执行)
  2. beforeCreate (等同于 setup 开始)
  3. created (等同于 setup 结束)

首次挂载

  1. beforeMount (同步)
  2. mounted (异步)

更新过程

  1. beforeUpdate (同步)
  2. updated (异步)

卸载过程

  1. beforeUnmount (同步)
  2. unmounted (异步)

特点:

  1. 每个阶段都有组件级和 vnode 级的钩子函数
  2. beforeXXX 钩子都是同步执行的
  3. 其他钩子都是通过 queuePostRenderEffect 异步执行的
  4. 钩子函数的执行顺序是确定的,有助于我们控制代码逻辑
  5. setup 是一个特殊的钩子,它在组件实例创建之前执行
  6. Composition API 中 beforeCreate 和 created 被 setup 替代
  7. 特殊钩子针对特定场景提供了额外的生命周期控制
  8. 开发环境提供了更多的调试钩子

这种设计让我们能够在组件生命周期的不同阶段执行需要的逻辑,同时通过同步/异步执行的区分,保证了性能和正确性。

相关推荐
adminwolf13 分钟前
基于Vue.js和Golang构建高效在线客服系统:前端实现与后端交互详解
前端·vue.js·golang
二哈喇子!2 小时前
Vue3生命周期
前端·javascript·vue.js
运维帮手大橙子5 小时前
完整的登陆学生管理系统(配置数据库)
java·前端·数据库·eclipse·intellij-idea
_Kayo_6 小时前
CSS BFC
前端·css
二哈喇子!7 小时前
Vue3 组合式API
前端·javascript·vue.js
二哈喇子!9 小时前
Vue 组件化开发
前端·javascript·vue.js
chxii9 小时前
2.9 插槽
前端·javascript·vue.js
姑苏洛言10 小时前
扫码点餐小程序产品需求分析与功能梳理
前端·javascript·后端
Freedom风间10 小时前
前端必学-完美组件封装原则
前端·javascript·设计模式
江城开朗的豌豆10 小时前
React表单控制秘籍:受控组件这样玩就对了!
前端·javascript·react.js