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

news2024/9/23 1:36:32

生命周期

在这里插入图片描述

正常的生命周期

// 注册钩子函数
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)
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ARM Trace32(劳特巴赫) 使用介绍 2.3 -- TRACE32 进阶命令之 参数传递介绍】

请阅读【ARM Coresight SoC-400/SoC-600 专栏导读】 文章目录 参数传递命令 ENTRY 参数传递命令 ENTRY ENTRY <parlist>The ENTRY command can be used to Pass parameters to a PRACTICE script or to a subroutineTo return a value from a subroutine 使用示例&am…

Android Studio 安装及使用

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

数值的整数次方Pow(x,n)

求数组的整数次方 思想&#xff1a; 分而治之 首先判断正负数&#xff0c;然后判断奇偶性问题&#xff1a; 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能&a…

DP1332E/DP1363F国产多协议NFC读写器芯片支持ISO15693/ISO18092

目录 ISO/IEC 15693与ISO/IEC 18092协议标准差异DP1363F与DP1332E对比共同点主要差异点 ISO/IEC 15693与ISO/IEC 18092协议标准差异 ISO/IEC 15693是用于近距离无线通信中的射频识别&#xff08;RFID&#xff09;技术的标准协议&#xff0c;它定义了与读写器之间的通信协议。这…

AnyTXT Searcher:本地文件内容搜索神器如何搭建与远程访问

文章目录 前言1. AnyTXT Searcher1.1 下载安装AnyTXT Searcher 2. 下载安装注册cpolar3. AnyTXT Searcher设置和操作3.1 AnyTXT结合cpolar—公网访问搜索神器3.2 公网访问测试 4. 固定连接公网地址 前言 你是否遇到过这种情况&#xff0c;异地办公或者不在公司&#xff0c;想找…

Python脚本工具,13个简单高效python脚本分享~先添加收藏,以备留用。

文章目录 前言1.使用 Python 进行速度测试2.在谷歌上搜索3.制作网络机器人4.获取歌曲歌词5.获取照片的Exif数据6.提取图像中的 OCR 文本7.将照片转换为Cartonize8.清空回收站9.Python 图像增强10.获取 Window 版本11.将 PDF 转换为图像12.转换十六进制到 RGB13.网站状态关于Pyt…

v-if与v-show

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:v-if与v-show 目录 v-if与v-show区别 一、v-show与v-if的共同点 二、v-show与v-if的区别 三、v…

openEuler 22.03 制作openssh9.5p1rpm包

1、yum安装编译依赖的组件 yum install -y rpm-build gcc gcc-c glibc glibc-devel openssl-devel openssl pcre-devel zlib zlib-devel make wget krb5-devel pam-devel libX11-devel libXt-devel initscripts libXt-devel gtk2-devel lrzsz 虚拟机配置可参考本地yum源 2…

使用VSCode调试全志R128的C906 RISC-V核心

使用 VSCode 调试 调试 XuanTie C906 核心 准备工具 T-Head DebugServer&#xff08;CSkyDebugServer&#xff09; - 搭建调试服务器 下载地址&#xff1a;T-Head DebugServer手册&#xff1a;T-Head Debugger Server User Guide驱动&#xff1a;cklink_dirvers VSCode - 开…

TikTok Shop印尼站关闭给所有跨境卖家的警示(附止损解决方案)

01关店事件回顾 10月3日&#xff0c;印尼TikTok卖家迎来了他们的至暗时刻&#xff1a;官方邮件通知明确告知所有人&#xff0c;TikTok Shop于10月4日印尼西部时间17点正式关闭。 至此&#xff0c;TikTok在印尼成为不再拥有“电商功能”的短视频内容平台&#xff0c;所有人都可…

稀疏数组举例详解(Java形式表示)

稀疏数组 稀疏数组的定义稀疏数组的形式稀疏数组的用途Java形式实现稀疏数组1.创建稀疏矩阵2.遍历矩阵提取有效元素个数&#xff0c;来确立稀疏数组创建的行数&#xff08;行数有效元素个数1&#xff09;3.通过有效元素个数创建稀疏数组的个数4.填入初始行&#xff08;第一行&a…

2023 年最新 MySQL 数据库 Windows 本地安装、Centos 服务器安装详细教程

MySQL 基本概述 MySQL是一个流行的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;广泛应用于各种业务场景。它是由瑞典MySQL AB公司开发&#xff0c;后来被Sun Microsystems收购&#xff0c;最终被甲骨文公司&#xff08;Oracle Corporation&#xff09;收购…

求二叉树的宽度(可执行)

输入&#xff1a;abd##e##cf### 输出结果&#xff1a;3 运行环境.cpp 注意&#xff1a;若无运行结果&#xff0c;则一定是建树错误 #include "bits/stdc.h" using namespace std; typedef struct BiTNode{char data;struct BiTNode *lchild,*rchild; }BiTNode,*Bi…

day30_servlet

今日内容 零、复习昨日 一、接收请求 二、处理响应 三、综合案例 零、复习昨日 画图, 请求处理的完整流程(javaweb开发流程) 零、注解改造 WebServlet注解,相当于是在web.xml中配置的servlet映射 Servlet类 package com.qf.servlet;import javax.servlet.ServletException; im…

leetcode:914. 卡牌分组(python3解法)

难度&#xff1a;简单 给定一副牌&#xff0c;每张牌上都写着一个整数。 此时&#xff0c;你需要选定一个数字 X&#xff0c;使我们可以将整副牌按下述规则分成 1 组或更多组&#xff1a; 每组都有 X 张牌。组内所有的牌上都写着相同的整数。 仅当你可选的 X > 2 时返回 tru…

作为测试你连K8S都不懂,你还有脸说自己是测试?

kubernetes&#xff0c;简称 K8s&#xff0c;是用 8 代替中间 8 个字符 “ubernete” 而成的缩写&#xff0c;是一个开源的&#xff0c;用于管理云平台中多个主机上的容器化的应用&#xff0c;Kubernetes 的目标是让部署容器化的应用简单并且高效(powerful)&#xff0c;Kuberne…

vue3之echarts区域折线图

vue3之echarts区域折线图 效果&#xff1a; 核心代码&#xff1a; <template><div class"abnormal"><div class"per">单位&#xff1a;{{ obj.data?.unit }}</div><div class"chart" ref"chartsRef"&g…

如何在Linux以docker-compose方式快速部署运行StackEdit,并实现公网访问

文章目录 1. docker部署Stackedit2. 本地访问3. Linux 安装cpolar4. 配置Stackedit公网访问地址5. 公网远程访问Stackedit6. 固定Stackedit公网地址 StackEdit是一个受欢迎的Markdown编辑器&#xff0c;在GitHub上拥有20.7k Star&#xff01;&#xff0c;它支持将Markdown笔记保…

井下特种兵——智能管网监测终端

深入井下&#xff0c;守护每一滴水的智慧——井下特种兵&#xff0c;智能管网监测终端 你是否曾为地下管网的监测和维护感到困扰&#xff1f;现在&#xff0c;我们向你介绍一款强大的井下特种兵——智能管网监测终端。这是一款专门为解决地下管网监测难题而设计的智能设备&…

单/三相dq解耦控制与特定次谐波抑制

1. 单相整流器dq坐标系下建模 单相整流器的拓扑如图所示&#xff0c;可知 u a b u s − L d i s d t − R i s {u_{ab}} {u_{s}} - L\frac{{d{i_s}}}{{dt}} - R{i_s} uab​us​−Ldtdis​​−Ris​。   将电压和电流写成dq的形式。 { u s U s m sin ⁡ ( ω t ) i s I …