阿卡不拉阿卡不拉
Vue3
阿卡的博客
Vue3
阿卡的博客
  • Vue3

    • 快速入门

      • 搭建工程 👌
      • 模板语法
      • 响应式基础
      • 响应式常用 API
      • 计算属性
      • 类与样式绑定
      • 条件和列表渲染
      • 事件处理
      • 表单处理
      • 生命周期
      • 侦听器
      • 组件介绍
      • Props
      • 自定义事件
      • 组件v-model
      • 插槽
      • 前端路由介绍
      • KeepAlive内置组件
      • 状态管理库
      • 组件库介绍
    • 深入本质

      • 虚拟DOM本质
      • 模板的本质
      • 组件树和虚拟DOM树
      • 数据拦截的本质
      • 响应式数据的本质
      • 响应式的本质
      • 响应式和组件渲染
      • 实现响应式系统 1
      • 实现响应式系统 2
      • 图解EFFECT
      • 实现响应式系统 3
      • 手写computed
      • 手写watch
      • 指令的本质
      • 插槽的本质
      • v-model的本质
      • setup 语法标签
      • 组件生命周期
      • keepalive 生命周期
      • keepalive的本质
      • key的本质
    • 细节补充

      • 【Vue】属性透传
      • 【Vue】依赖注入
      • 【Vue】组合式函数 👌
      • 【Vue】自定义指令
      • 【Vue】插件
      • 【Vue】Transition
      • 【Vue】TransitionGroup
      • 【Vue】Teleport
      • 【Vue】异步组件
      • 【Vue】Suspense
      • 【Router】路由模式
      • 【Router】路由零碎知识
      • 【Router】路由匹配语法
      • 【Router】路由组件传参
      • 【Router】内置组件和函数
      • 【Router】导航守卫
      • 【Router】过渡特效
      • 【Router】滚动行为
      • 【Router】动态路由
      • 【State】通信方式总结
      • 【State】Pinia 自定义插件
      • 【场景】封装树形组件
      • 【场景】自定义 ref 实现防抖
      • 【场景】懒加载
      • 【场景】虚拟列表
      • 【场景】虚拟列表优化
      • 【第三方库】VueUse
      • 【第三方库】vuedragable
      • 【第三方库】vue-drag-resize
      • 【第三方库】vue-chartjs
      • 【第三方库】vuelidate
      • 【第三方库】vue3-lazyload
      • 【场景】Websocket 聊天室
      • 【Vite】✨ 认识 Vite👌
      • 【Vite】配置文件 👌
      • 【Vite】✨ 依赖预构建 👌
      • 【Vite】构建生产版本 👌
      • 【Vite】环境变量与模式
      • 【Vite】CLI
      • 【Vite】Vite 插件
  • 笔面试

    • HTML

      • HTML 面试题汇总
      • 文档声明
      • 语义化
      • W3C 标准组织
      • SEO
      • iframe
      • 微格式
      • 替换元素
      • 页面可见性
    • CSS

      • CSS 面试题汇总
      • CSS 单位总结
      • 居中方式总结
      • 隐藏元素方式总结
      • 浮动
      • 定位总结
      • BFC
      • CSS 属性计算过程
      • CSS 层叠继承规则总结
      • @import 指令
      • CSS3 calc 函数
      • CSS3 媒体查询
      • 过渡和动画事件
      • 渐进增强和优雅降级
      • CSS3 变形
      • 渐进式渲染
      • CSS 渲染性能优化
      • 层叠上下文
      • CSS3 遮罩
    • JavaScript

      • JavaScript 面试题汇总
      • ✨ let、var、const 的区别
      • JS中的数据类型
      • 包装类型
      • 数据类型的转换
      • 运算符
      • ✨ 原型链
      • ✨ this 指向
      • ✨ 垃圾回收与内存泄漏
      • ✨ 执行栈和执行上下文
      • ✨ 作用域和作用域链
      • ✨ 闭包
      • DOM 事件的注册和移除
      • DOM 事件的传播机制
      • 阻止事件默认行为
      • 递归
      • ✨ 属性描述符
      • class 和构造函数区别
      • 浮点数精度问题
      • 严格模式
      • ✨ 函数防抖和节流
      • ✨ WeakSet 和 WeakMap
      • ✨ 深浅拷贝
      • 函数柯里化
      • Node 事件循环
      • 尺寸和位置
    • 浏览器

      • 浏览器面试题汇总
      • ✨ 浏览器的渲染流程
      • ✨ 资源提示关键词
      • 浏览器的组成部分
      • IndexedDB
      • ✨ File API
      • ✨ 浏览器缓存
      • ✨ 浏览器跨标签页通信
      • Web Worker
    • 网络

      • 网络面试题汇总
      • 五层网络模型 👌
      • 常见请求方法 👌
      • ✨cookie👌
      • 面试题
      • 加密
      • ✨JWT👌
      • ✨ 同源策略及跨域问题 👌
      • 文件上传
      • ✨ 输入 url 地址之后
      • 文件下载
      • ✨ session
      • ✨ TCP
      • ✨ CSRF 攻击
      • ✨XSS 攻击 👌
      • ✨ 网络性能优化
      • 断点续传
      • 域名和 DNS
      • SSL、TLS、HTTPS 的关系
      • ✨ HTTP 各版本差异 👌
      • HTTP 缓存协议
      • ✨ WebSocket
    • 工程化

      • CMJ 和 ESM
      • npx
      • ESLint
    • Vue2

      • Vue 面试题汇总相关
      • 组件通信方式总结
      • 虚拟 DOM
      • v-model
      • 数据响应式原理
      • diff
      • 生命周期详解
      • computed
      • 过滤器
      • 作用域插槽
      • 过度和动画
      • 优化
      • keep-alive
      • 长列表优化
      • 其他 API
      • 模式和环境变量
      • 更多配置
      • 更多命令
      • 嵌套路由
      • 路由切换动画
    • Vue3

      • ✨ Vue3 整体变化 👌
      • ✨ Vue3 响应式变化 👌
      • ✨ nextTick 实现原理 👌
      • 两道代码题 👌
      • Vue 运行机制
      • 渲染器核心功能
      • 事件绑定与更新

组件生命周期

官方生命周期图:

完整生命周期

这里分为这么几个大的阶段:

  1. 初始化选项式 API
  2. 模板编译
  3. 初始化渲染
  4. 更新组件
  5. 销毁组件

1. 初始化选项式API

当渲染器遇到一个组件的时候,首先是初始化选项式 API,这里在内部还会涉及到组件实例对象的创建。

在组件实例对象创建的前后,就对应着一组生命周期钩子函数:

  • 组件实例创建前:setup、beforeCreate
  • 组件实例创建后:created

2. 模板编译

接下来会进入模板编译的阶段,当模板编译的工作结束后,会执行 beforeMount 钩子函数。

3. 初始化渲染

接下来是初始化渲染,到了这个阶段,意味着已经生成了真实的 DOM. 完成初始化渲染后会执行 mounted 生命周期方法。

4. 更新组件

更新组件时对应着一组生命周期钩子方法:

  • 更新前:beforeUpdate
  • 更新后:updated

5. 销毁组件

销毁组件时也对应一组生命周期钩子方法:

  • 销毁前:beforeUnmount
  • 销毁后:unmounted

一般在销毁组件时我们会做一些清理工作,例如清除计时器等操作。

另外需要注意在 Vue3 中生命周期的钩子函数的名字和上面所介绍的生命周期稍微有一些区别:

生命周期名称Vue2Vue3
beforeCreate 阶段beforeCreatesetup
created 阶段createdsetup
beforeMount 阶段beforeMountonBeforeMount
mounted 阶段mountedonMounted
beforeUpdate 阶段beforeUpdateonBeforeUpdate
updated 阶段updatedonUpdated
beforeUnmount 阶段beforeDestroyonBeforeUnmount
unmounted 阶段destoryedonUnmounted

Vue2 和 Vue3 的生命周期钩子方法是可以共存的,这意味着你在一个组件中可以写 mounted 和 onMounted,Vue3 的生命周期钩子函数的执行时机会比 Vue2 对应的生命周期钩子函数要早一些,不过一般没人会这么写。

生命周期的本质

所谓生命周期,其实就是在合适的时机调用用户所设置的回调函数。

首先需要了解组件实例和组件挂载。假设用户书写了这么一个组件:

export default {
  name: 'UserCard',
  props: {
    name: String,
    email: String,
    avatarUrl: String
  },
  data(){
    return {
      foo: 1
    }
  },
  mounted() {
    // ...
  },
  render() {
    return h('div', { class: styles.userCard }, [
      h('img', {
        class: styles.avatar,
        src: this.avatarUrl,
        alt: 'User avatar'
      }),
      h('div', { class: styles.userInfo }, [h('h2', this.name), h('p', this.email)])
    ])
  }
}

那么这些内容实际上是一个选项对象,回头在渲染这个组件的时候,某些信息是会被挂到组件实例上面的。组件实例本质就是一个对象,该对象维护着组件运行过程中的所有信息,例如:

  • 注册到组件的生命周期钩子函数
  • 组件渲染的子树
  • 组件是否已经被挂载
  • 组件自身的状态
function mountComponent(vnode, container, anchor) {
  // 获取选项对象
  const componentOptions = vnode.type;
  // 从选项对象上面提取出 render 以及 data
  const { render, data } = componentOptions;

  // 创建响应式数据
  const state = reactive(data());

  // 定义组件实例,一个组件实例本质上就是一个对象,它包含与组件有关的状态信息
  const instance = {
    // 组件自身的状态数据,即 data
    state,
    // 一个布尔值,用来表示组件是否已经被挂载,初始值为 false
    isMounted: false,
    // 组件所渲染的内容,即子树(subTree)
    subTree: null,
  };

  // 将组件实例设置到 vnode 上,用于后续更新
  vnode.component = instance;

  // 后面逻辑略...
}

下面是组件挂载:

function mountComponent(vnode, container, anchor) {
  // 前面逻辑略...
  
  effect(
    () => {
      // 调用组件的渲染函数,获得子树
      const subTree = render.call(state, state);
      // 检查组件是否已经被挂载
      if (!instance.isMounted) {
        // 初次挂载,调用 patch 函数第一个参数传递 null
        patch(null, subTree, container, anchor);
        // 重点:将组件实例的 isMounted 设置为 true,这样当更新发生时就不会再次进行挂载操作,
        // 而是会执行更新
        instance.isMounted = true;
      } else {
        // 当 isMounted 为 true 时,说明组件已经被挂载,只需要完成自更新即可,
        // 所以在调用 patch 函数时,第一个参数为组件上一次渲染的子树,
        // 意思是,使用新的子树与上一次渲染的子树进行打补丁操作
        patch(instance.subTree, subTree, container, anchor);
      }
      // 更新组件实例的子树
      instance.subTree = subTree;
    },
    { scheduler: queueJob }
  );
}

其核心就是根据组件实例的 isMounted 属性来判断该组件是否是初次挂载:

  • 初次挂载:patch 的第一个参数为 null;会设置组件实例 isMounted 为 true
  • 非初次挂载:更新组件的逻辑,patch 的第一个参数是组件上一次渲染的子树,从而和新的子树进行 diff 计算

所谓生命周期,就是在合适的时机执行用户传入的回调函数。

function mountComponent(vnode, container, anchor) {
  const componentOptions = vnode.type;
  // 从组件选项对象中取得组件的生命周期函数
  const {
    render,
    data,
    beforeCreate,
    created,
    beforeMount,
    mounted,
    beforeUpdate,
    updated,
  } = componentOptions;
  
  // 拿到生命周期钩子函数之后,就会在下面的流程中对应的位置调用这些钩子函数

  // 在这里调用 beforeCreate 钩子
  beforeCreate && beforeCreate();

  const state = reactive(data());

  const instance = {
    state,
    isMounted: false,
    subTree: null,
  };
  vnode.component = instance;

  // 组件实例已经创建
  // 此时在这里调用 created 钩子
  created && created.call(state);

  effect(
    () => {
      const subTree = render.call(state, state);
      if (!instance.isMounted) {
        // 在这里调用 beforeMount 钩子
        beforeMount && beforeMount.call(state);
        patch(null, subTree, container, anchor);
        instance.isMounted = true;
        // 在这里调用 mounted 钩子
        mounted && mounted.call(state);
      } else {
        // 在这里调用 beforeUpdate 钩子
        beforeUpdate && beforeUpdate.call(state);
        patch(instance.subTree, subTree, container, anchor);
        // 在这里调用 updated 钩子
        updated && updated.call(state);
      }
      instance.subTree = subTree;
    },
    { scheduler: queueJob }
  );
}

在上面的代码中,首先从组件的选项对象中获取到注册到组件上面的生命周期函数,然后内部会在合适的时机调用它们。

嵌套结构下的生命周期

组件之间是可以进行嵌套的,从而形成一个组件树结构。那么当遇到多组件嵌套的时候,各个组件的生命周期是如何运行的呢?

实际上非常简单,就是一个递归的过程。

假设 A 组件下面嵌套了 B 组件,那么渲染 A 的时候会执行 A 的 onBeforeMount,然后是 B 组件的 onBeforeMount,然后 B 正常挂载,执行 B 组件的 mounted,B 渲染完成后,接下来才是 A 的 mounted.

  1. 组件 A:onBeforeMount
  2. 组件 B:onBeforeMount
  3. 组件 B:mounted
  4. 组件 A:mounted

倘若涉及到组件的销毁,也同样是递归的逻辑。

最近更新:: 2025/7/9 07:29
Contributors: AK
Prev
setup 语法标签
Next
keepalive 生命周期