Vue3 源码解读系列(八)——生命周期

生命周期

在这里插入图片描述

正常的生命周期

// 注册钩子函数
const onBeforeMount = createHook('bm'/* BEFORE_MOUNT */)
const onMounted = createHook('m'/* MOUNTED */)
const onBeforeUpdate = createHook('bu'/* BEFORE_UPDATE */)
const onUpdated = createHook('u'/* UPDATED */)
const onBeforeUnmount = createHook('bum'/* BEFORE_UNMOUNT */)
const onUnmounted = createHook('um'/* UNMOUNTED */)
const onRenderTriggered = createHook('rtg'/* RENDER_TRIGGERED */)
const onRenderTracked = createHook('rtc'/* RENDER_TRACKED */)
const onErrorCaptured = (hook, target = currentInstance) => {
  injectHook('ec'/* ERROR_CAPTURED */, hook, target)
}

/**
 * 创建钩子函数
 * createHook 只是对 injectHook 的封装,区别只有第一个参数不同
 */
function createHook(lifecycle) {
  return function (hook, target = currentInstance) {
    // 通过 injectHook 注册钩子函数
    injectHook(lifecycle, hook, target)
  }
}

/**
 * 注册钩子函数
 * injectHook 主要是对用户注册的 hook 进行封装,然后添加到一个数组中,把数组保存到当前组件实例 target 上
 * 钩子函数必须要保存在当前的组件实例上,通过不同的字符串 key 找到对应的钩子函数数组并执行
 */
function injectHook(type, hook, target = currentInstance, prepend = false) {
  const hooks = target[type] || (target[type] = [])

  // 封装 hook 钩子函数并缓存
  const wrappedHook = hook.__weh || (hook.__weh = (...args) => {
    if (target.isUnmounted) return

    // 停止依赖收集(因为执行生命周期时往往已经执行过依赖收集了,所以不需要在执行生命周期时继续依赖收集,这会损耗性能)
    pauseTracking()

    // 在钩子函数执行的时候,为了确保此时的 currentInstance 和注册钩子函数是一致的,会通过 setCurrentInstance 设置 target 为当前的组件实例
    setCurrentInstance(target)

    // 执行钩子函数
    const res = callWithAsyncErrorHandling(hook, target, type, args)
    setCurrnetInstance(null)

    // 恢复依赖收集
    resetTracking()
    return res
  })
  if (prepend) {
    hooks.unshift(wrappedHook)
  } else {
    hook.push(wrappedHook)
  }
}

/**
 * 组件副作用渲染函数
 */
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
  // 创建响应式的副作用渲染函数
  instance.update = effect(function componentEffect() {
    // 组件挂载部分
    // 父 beforeMount -> 子 beforeMount -> 子 mounted -> 父 mounted
    if (!instance.isMounted) {
      // 获取组件实例上通过 onBeforeMount 钩子函数和 onMounted 注册的钩子函数
      const { bm, m } = instance

      // 渲染组件生成子树 vnode
      const subTree = (instance.subTree = renderComponentRoot(instance))

      // 执行 beforeMount 钩子函数
      if (bm) {
        // bm 是一个数组,因为用户可以通过指定多个 onBeforeMount 函数注册多个钩子函数,因此这里是遍历数组依次执行
        invokeArrayFus(bm)
      }

      // 把子树 vnode 挂载到 container 中
      patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)

      // 保留渲染生成的子树根 DOM 节点
      initialVNode.el = subTree.el

      // 执行 mounted 钩子函数
      if (m) {
        queuePostRenderEffect(m, parentSuspense)
      }
      instance.isMounted = true
    }
    // 组件更新部分
    // 组件的更新只涉及当前组件
    else {
      // 获取组件实例上通过 onBeforeUpdate 钩子函数和 onUpdated 注册的钩子函数
      let { next, vnode, bu, u } = instance

      // next 表示新的组件 vnode
      if (next) {
        // 更新组件 vnode 节点信息
        updateComponentPreRender(instance, next, optimized)
      } else {
        next = vnode
      }

      // 渲染新的子树 vnode
      const nextTree = renderComponentRoot(instance)

      // 缓存旧的子树 vnode
      const prevTree = instance.subTree

      // 更新子树 vnode
      instance.subTree = nextTREE

      // 执行 beforeUpdate 钩子函数
      if (bu) {
        invokeArrayFns(bu)
      }

      // 组件更新核心逻辑,根据新旧子树 vnode 做 patch
      patch(prevTree, nextTree,
        // 如果在 teleport 组件中父节点可能已经改变,所以容器直接找旧树 DOM 元素的父节点
        hostParentNode(prevTree.el),
        // 缓存更新后的 DOM 节点
        getNextHostNode(prevTree), instance, parentSuspense, isSVG)

      // 缓存更新后的 DOM 节点
      next.el = nextTree.el

      // 执行 updated 钩子函数
      // 注意:不要在 updated 钩子函数中更改数据,因为会再次触发组件更新!!!
      if (u) {
        queuePostRenderEffect(u, parentSuspense)
      }
    }
  }, prodEffectOptions)
}

/**
 * 组件副作用渲染函数 - 组件销毁部分
 * 父 beforeUnmount -> 子 beforeUnmount -> 子 unmounted -> 父 unmounted
 */
const unmountComponent = (instance, parentSuspense, doRemove) => {
  const { bum, effects, update, subTree, um } = instance

  // 执行 beforeUnmount 钩子函数
  if (bum) {
    invokeArrayFns(bum)
  }

  // 清理组件引用的 effects 副作用函数
  if (effects) {
    for (let i = 0; i < effects.length; i++) {
      stop(effects[i])
    }
  }

  // 如果一个异步组件在加载前就销毁了,则不会注册副作用渲染函数
  if (update) {
    stop(update)

    // 调用 unmount 销毁子树
    unmount(subTree, instance, parentSuspense, doRemove)
  }

  // 执行 unmounted 钩子函数,通过递归的方式遍历子树销毁子节点
  if (um) {
    queuePostRenderEffect(um, parentSuspense)
  }
}

捕获后代组件错误的生命周期

/**
 * 捕获后代组件的错误 - onErrorCaptured
 */
function handleError(err, instance, type) {
  const contextVNode = instance ? instance.vnode : null
  if (instance) {
    let cur = instance.parent
    const exposedInstance = instance.proxy // 为了兼容 2.x 版本,暴露组件实例给钩子函数

    // 获取错误信息
    const errorInfo = (process.env.NODE_ENV !== 'production') ? ErrorTypeStrings[type] : type

    // 尝试向上查找所有父组件,执行 errorCaptured 钩子函数
    while (cur) {
      const errorCapturedHooks = cur.ec
      // 如果存在则遍历执行
      if (errorCapturedHooks) {
        for (let i = 0; i < errorCapturedHooks.length; i++) {
          // 判断 errorCaptured 钩子函数返回 true,则停止向上查找
          if (errorCapturedHooks[i](err, exposedInstance, errorInfo)) return
        }
      }
      cur = cur.parent
    }
  }
  // 如果整个链路上都没有能够正确处理错误的 errCaptured 钩子函数,则控制台输出未处理的错误
  logError(err, type, contextVNode)
}

开发环境下调试的生命周期

/**
 * 创建开发环境的副作用函数的配置
 * 实际上是在 track、trigger 阶段添加副作用函数并执行
 */
function createDevEffectOptions(instance) {
  return {
    scheduler: queueJob,
    onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc, e) : void 0,
    onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg, e) : void 0
  }
}

/**
 * track 的实现
 */
function track(target, type, key) {
  // 执行一些依赖收集的操作
  // ...

  // 执行完依赖收集后执行 onTrack 函数,遍历执行 renderTracked 钩子函数
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect)
    activeEffect.deps.push(dep)
    // 在非生产环境下检测当前 activeEffect 的配置有没有定义 onTrack 函数,如果有,则执行该方法
    if ((process.env.NODE_ENV !== 'production') && activeEffect.options.onTrack) {
      // 执行 onTrack 函数
      activeEffect.options.onTrack({
        effect: activeEffect,
        target,
        type,
        key
      })
    }
  }
}

/**
 * trigger 的实现
 */
function trigger(target, type, key, newValue) {
  // 添加要运行的 effects 集合,然后遍历执行
  // ...

  // 在非生产环境下检测待执行的 effect 配置是否定义 onTrigger 函数,如果有,则执行该方法
  const run = (effect) => {
    if ((process.env.NODE_ENV !== 'production') && effect.options.onTrigger) {
      // 执行 onTrigger
      effect.options.onTrigger({
        effect,
        target,
        key,
        type,
        newValue,
        oldValue,
        oldTarget
      })
    }

    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }

  // 遍历执行 effects
  effects.forEach(run)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/168532.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【中间件】中间件的宏观探讨漫谈

中间件探讨 内容管理 intro中间件和框架why use常用相关Middleware接入层服务层 本文主要是宏观上再次探讨一下中间件 cfeng之前单纯的分享过缓存、消息队列、还有就是Spring Cloud下面提供的一些中间件的使用&#xff0c;但是整体上就是感觉很松散的&#xff0c;所以cfeng现在…

金蝶云星空ScpSupRegHandler任意文件上传漏洞复现 [附POC]

文章目录 金蝶云星空ScpSupRegHandler任意文件上传漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 金蝶云星空ScpSupRegHandler任意文件上传漏洞复现 [附POC] 0x01 前言 免责声明&#xff1a;请…

江苏专转本考试时,遇到不会的题目该怎么办呢??

有很多同学最近在问&#xff0c;如果专转本考试时遇到 不会的题目怎么办&#xff1f;&#xff1f; 考场上题目太陌生没见过会不会凉凉 以学姐自身经验分享&#xff0c;其实未必会凉凉&#xff0c;当时我在16届计算机考试时&#xff0c;遇上了填空题新题型&#xff0c;当时在考…

武汉凯迪正大KDHG-220P互感器综合测试仪

主要特点 武汉凯迪正大KDHG-220P互感器综合测试仪&#xff0c;仅需进行简单的数字设定&#xff1a;设定互感器的额定参数。仪器将全过程自动记录数据&#xff0c;并自动将变比极性、伏安特性曲线等计算并显示出来&#xff0c;省去换线、手动调压、人工记录、整理、描曲线等烦琐…

【旅游行业】Axure旅游社交平台APP端原型图,攻略门票酒店民宿实战模板

作品概况 页面数量&#xff1a;共 110 页 兼容软件&#xff1a;Axure RP 9/10&#xff0c;不支持低版本 应用领域&#xff1a;旅游平台&#xff0c;酒店住宿 作品申明&#xff1a;页面内容仅用于功能演示&#xff0c;无实际功能 作品特色 本作品为「旅游社交平台」移动端…

单链表相关面试题--7.链表的回文结构

/* 解题思路&#xff1a; 此题可以先找到中间节点&#xff0c;然后把后半部分逆置&#xff0c;最近前后两部分一一比对&#xff0c;如果节点的值全部相同&#xff0c;则即为回文。 */ class PalindromeList { public:bool chkPalindrome(ListNode* A) {if (A NULL || A->ne…

【JS】BMI身体质量指数计算工具

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍BMI身体质量指数计算工具的JS实现。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次…

实施方法论

软件实施方法论(通用) 软件项目实施交付模型很多领域流传着别人家的传说&#xff1a;别人家的孩子学习成绩好才艺多还长得帅&#xff0c;别人家的客户钱多人傻速来&#xff0c;别人家的公司不加班产品做得好工资还发的倍儿高。这一切事情实际上都有常量和变量&#xff0c;比如…

iptables的一次修复日志

iptables的一次修复日志 搭建配置wireguard后&#xff0c;使用内网连接设备十分方便&#xff0c;我采用的是星型连接&#xff0c;即每个节点都连接到中心节点&#xff0c;但是突然发生了重启wg后中心节点不转发流量的问题&#xff0c;即每个接入的节点只能与中心节点连接&…

请收好|实验室仪器管理知识大全

实验室的仪器设备是直接用于提供检测结果或辅助检测进行的&#xff0c;是实验室的重要资产&#xff0c;也是重要的检测工具。对检测结果的准确性和可靠性起到至关重要的作用。如何保持仪器设备的有效性和可靠性&#xff0c;使仪器设备处于完好的状态&#xff0c;在产品质量检测…

使用Dockerfile构建hexo博客镜像,并部署

基于centos7.9父镜像构建hexo:1.0镜像&#xff1a; cat << eof > Dockerfile # 根镜像 FROM centos:7.9.2009 MAINTAINER qv123<qv1095322098163.com> # 设置工作目录 WORKDIR /usr/src/app # 暴露端口号 EXPOSE 4000 # 作者注释 MAINTAINER qv123<qv1095322…

“绵柔的,好喝的”海之蓝畅销20年的经典秘诀:做大众喜爱的好酒

执笔 | 尼 奥 编辑 | 萧 萧 在中国白酒历史长河中&#xff0c;有的品牌如大浪淘沙而灰飞烟灭&#xff0c;也有的白酒品牌因为不断创新而经久不衰。我们时常在思考一个产业命题&#xff1a;白酒品牌常青的秘诀到底是什么&#xff1f; 经过20多年的产业发展&#xff0c;中国…

壹基金宣传进瑞金河背街社区 安全家园项目防灾减灾深入人心

11月16日下午&#xff0c;瑞金赋能公益、蓝天救援队等联合象湖镇河背街社区开展家庭安全计划社区活动包挑战赛活动暨壹基金安全家园项目防灾减灾宣传社区行活动。活动得到了救助儿童会北京代表处、壹基金、艾特公益、益心益意公益的指导&#xff0c;得到了阿里巴巴公益平台广大…

【数据结构】链表的八种形态

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 链表的三大"性状" 一.带头链表和不带头链表 头指针与头结点的异同 头指针 头结点 二.循环链表和非循环链表 三.双向链表和单向链表 链表的八大形态 结语…

键盘快捷键工具Keyboard Maestro mac中文版介绍

Keyboard Maestro mac是一款键盘快捷键工具&#xff0c;它可以帮助用户通过自定义快捷键来快速完成各种操作&#xff0c;提高工作效率。Keyboard Maestro支持多种快捷键组合&#xff0c;包括单键、双键、三键、四键组合等&#xff0c;用户可以根据自己的习惯进行设置。此外&…

测试工程师一定要会的Jmeter_性能测试教程:性能测试脚本的优化

性能测试脚本的优化 以PHP论坛为例&#xff1a;http://47.107.178.45/phpwind/ 根据上一篇的性能测试(3&#xff09;的脚本进行优化&#xff1b;见下图&#xff1a; 如上图中&#xff0c;把发帖和回帖的事务添加到随机控制器中&#xff0c;登录操作添加到仅一次控制器中&…

修改el-radio-group样式,自定义单选组件

修改el-radio-group样式,自定义单选组件 自定义组件 MyRadioGroup.vue <template><div class"btnsBox"><el-radio-group v-model"activeIndex" change"handleClick"><el-radio-buttonv-for"(item, index) in list&qu…

万户OA upload任意文件上传漏洞复现

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品&#xff0c;统一的基础管理平台&#xff0c;实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台&#xff0c;将外网信息…

vite vue3配置axios

准备 参考 安装axios yarn add axiossrc下新建request文件夹&#xff0c;该文件下新建index.ts import axios from axios; import { ElMessage } from element-plus;// const errorCodeType function (code: number): string { // let errMessage: string 未知错误; // …

SqlServer_idea连接问题

问题描述&#xff1a; sqlServer安装之后可以使用navicat进行连接idea使用账户密码进行登录连接失败 问题解决&#xff1a; 先使用sqlServer管理工具进行登录 使用window认证连接修改账户密码 启用该登录名 这时idea还是无法连接&#xff0c;还需要如下配置 打开sqlserve…