vue3 源码解析(5)— patch 函数源码的实现

news2024/11/29 2:32:11

什么是 patch

在 vue 中 patch 函数的作用是在渲染的过程中,比较新旧节点的变化,通过打补丁的形式,进行新增、删除、移动或替换操作,此过程避免了大量的 dom 操作,提升了运行的性能。

patch 执行流程

patch 函数整体流程比较长,函数内部包含很多分支用于处理不同的节点(Text、ELEMENT、COMPONENT)。

为了便于理解,文章中的代码皆为简化之后的代码:

/**
 * 
 * @param n1 上一次渲染的 vnode
 * @param n2 当前需要渲染的 vnode
 * @param container 容器
 * @param anchor 锚点, 用来标记插入的位置
 * @returns 
 */
const patch = (n1, n2, container, anchor = null) => {
  // 没变化不用对比,跳过此处节点
  if (n1 === n2) {
    return
  }

  // 如果type以及key值不一样,则删除此就节点
  if (n1 && !isSameVNodeType(n1, n2)) {
    const parent = n1.el.parentNode
    if (parent) {
      parent.removeChild(n1.el) // 删除元素
    }
    n1 = null // 删除之后重新加载
  }

  // 通过节点的shapeFlag(描述该组件的类型)进行不同处理
  const { shapeFlag, type } = n2
  if (type === Text) { // 文本
    console.log('文本')
    processText(n1, n2, container)
  } else if (shapeFlag & ShapeFlags.ELEMENT) { // 元素
    console.log('元素')
    processElement(n1, n2, container, anchor)
  } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { // 组件
    console.log('组件')
    processComponent(n1, n2, container)
  }
}

// 对比新旧的type值以及key值
const isSameVNodeType = (n1, n2) => {
  return n1.type === n2.type && n1.key === n2.key
}

processText

processText 函数用于处理文本节点的更新。

函数根据新旧 vnode 节点的情况,创建新的文本节点或更新现有的文本节点的内容,以确保文本内容正确地显示在 DOM 中。

const processText = (n1, n2, container) => {
  const textNode = document.createTextNode(n2.children)
  // 旧的 vnode 节点不存在
  if (n1 == null) {
    // 创建文本节点并将内容设置为新节点的文本内容
    insert(createText(n2.children), container)
  } else {
    // 更新现有的文本节点的内容
    n1.el.textContent = textNode
  }
}

processElement

processElement 函数用于处理元素节点的更新。

函数根据新旧 vnode 节点的差异,对元素节点进行更新,并处理元素节点的属性、样式、事件等方面的变化。

const processElement = (n1, n2, container, anchor) => {
  if (n1 == null) { // 旧的 vnode 节点不存在
    // 创建新的元素节点
    mountElement(n2, container, anchor)
  } else { // 更新现有的元素节点
    console.log('同一元素的比对')
    patchElement(n1, n2, container, anchor)
  }
}

mountElement

mountElement 用于挂载新的元素节点到 DOM 中。

const mountElement = (vnode, container, anchor) => {
  const { type, props, shapeFlag, children } = vnode
  // 创建一个新的元素节点 el,并将其设置为 vnode节点的 el 属性,便于在后续的更新中可以引用到该元素节点
  const el = vnode.el = document.createElement(type)
  if (props) { // 添加属性
    // 遍历设置元素节点的属性
    for (const key in props) {
      el.setAttribute(key, props[key])
    }
  }
  // 处理children
  if (children) {
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { // 子组件是纯文本
      // 创建文本元素
      setElementText(el, children)
    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { // 子组件是数组列表
      // 递归挂载子节点
      mountChildren(el, children)
    }
  }
  // 将元素节点挂载到容器中
  container.insertBefore(el, anchor || null)
}

mountChildren

mountChildren 函数的作用是将一组子节点挂载到指定的父节点中。

const mountChildren = (el, children) => {
  for (let i = 0; i < children.length; i++) {
    const child = normalizeVNode(children[i]) // 对当前子节点进行规范化处理,确保它是一个虚拟节点对象  
    patch(null, child, el) // 因为旧的节点为null(表示之前没有任何节点),所以会直接创建并挂载新的子节点到 el 中
  }
}

// 规范化虚拟节点,确保它们都是预期的对象格式
const normalizeVNode = (child) => {
  if (isObject(child)) { // 已经是一个规范的 vnode
    return child
  } else {
    return createVNode(Text, null, String(child)) // 创建一个文本类型的 vnode
  }
}

patchElement

patchElement 用于更新已存在的元素节点。

函数的作用是根据新旧 vnode 节点的差异,对已存在的元素节点进行更新。

const patchElement = (n1, n2, container, anchor) => {
  const el = (n2.el = n1.el) // 获取真实节点
  const oldProps = n1.props || {}
  const newProps = n2.props || {}
  for (const key in newProps) {
    const oldValue = oldProps[key]
    const newValue = newProps[key]
    if (oldValue !== newValue) {
      el.setAttribute(key, newValue)
    }
  }
  // 比对children
  patchChildren(n1, n2, el)
}

const patchChildren = (n1, n2, el) => {
  const c1 = n1.children
  const c2 = n2.children
  const prevShapeFlag = n1.shapeFlag
  const nextShapeFlag = n2.shapeFlag
  if (nextShapeFlag & ShapeFlags.TEXT_CHILDREN) { // 文本类型
    // 直接设置元素的文本内容为新的子节点内容
    setElementText(el, c2)
  } else {
    if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { // 新旧子节点都是数组
      patchKeyedChildren(c1, c2, el) // diff算法 使用双指针策略来更新子节点
    } else { // 如果旧的子节点是文本,但新的子节点是一个数组
      setElementText(el, '') // 首先,清空旧元素的文本内容 
      mountChildren(el, c2) // 然后,挂载新的子节点到DOM元素上
    }
  }
}

这里的 patchKeyedChildren 是 patch 函数最为重要的内容。函数内部实现了核心的 diff 算法,在后面的文章中会重点介绍。

processComponent

processComponent 用于处理组件类型的虚拟 DOM 节点。

函数的作用是处理组件类型的虚拟 DOM 节点,包括创建组件实例、挂载组件、更新组件等操作。

const processComponent = (n1, n2, container) => {
  if (n1 === null) { // 首次挂载
    mountComponent(n2, container)
  } else { // 更新
    updateComponent(n1, n2, container)
  }
}

mountComponent

mountComponent 函数用于挂载组件类型的虚拟 DOM 节点。

函数的作用是创建组件实例、解析组件实例对象并设置渲染效果。

// 组件挂载
const mountComponent = (initialVNode, container) => {
  const instance = initialVNode.component = createComponentInstance(
    initialVNode) // 初始化一个组件的实例对象
  setupComponent(instance) // 解析组件的实例对象
  setupRenderEffect(instance, container) // 创建一个effect
}
  • createComponentInstance: 初始化一个组件的实例对象,添加相关属性(vnode、type、props、attrs、ctx、proxy)。

  • setupComponent: 函数来解析组件的实例对象。这个函数会处理组件的 props、slots、attrs 等属性,并设置响应式数据等。它会在组件实例上创建一些与组件相关的属性和方法。

  • setupRenderEffect: 创建一个 effect,用于在组件状态变化时触发重新渲染。该 effect 依赖于组件实例的响应式数据。当响应式数据发生变化时,会触发该 effect,进而调用组件实例的 render 方法重新渲染组件,并将渲染结果插入到 container 容器中。

updateComponent

updateComponent 函数用于更新组件类型的虚拟 DOM 节点。

函数根据新旧节点的变化情况,更新组件的状态和属性,并触发组件的生命周期钩子函数。通过更新组件的 props、slots 等属性,执行 beforeUpdate 生命周期钩子函数。执行组件的更新操作,最后执行 updated 生命周期钩子函数,完成了组件的更新过程。

function updateComponent(n1, n2, container) {
  const instance = n2.component = n1.component // 组件实例引用进行传递,确保组件实例的一致性
  if (shouldUpdateComponent(n1, n2)) { // 判断是否需要更新组件
    // 更新组件的 props、slots 等属性
    instance.props = n2.props
    instance.slots = n2.slots
    // 触发 beforeUpdate 生命周期钩子函数
    callBeforeUpdateHooks(instance)
    // 执行组件的更新操作
    instance.update()
    // 触发 updated 生命周期钩子函数
    callUpdatedHooks(instance)
  }
}
  • shouldUpdateComponent: 函数判断是否需要更新组件。该函数会比较新旧节点的属性、事件等是否发生了变化,以确定是否需要进行组件的更新操作。如果判断为需要更新组件,则执行下面的操作;否则,不进行任何更新操作。

  • callBeforeUpdateHooks: 函数触发组件的 beforeUpdate 生命周期钩子函数。该函数会在组件更新之前执行一些预处理或清理操作。

  • update: 执行组件的更新操作,包括重新渲染组件。

  • callUpdatedHooks: 函数触发组件的 updated 生命周期钩子函数。该函数会在组件更新完成后执行一些操作,如更新后的 DOM 操作、发送请求等。

总结

在这里插入图片描述

为了便于理解我把以上代码流程流程图的形式展示出来了,希望通过本篇文章可以帮助读者更好的了解 vue 的渲染过程。

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

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

相关文章

0-MQTT基础使用教程【学习】

文件路径 MQTT基础使用教程1. MQTT1.1 MQTT简介1.1.1 什么是MQTT1.1.2 设计原则1.1.3 应用领域1.2 MQTT协议相关概念1.2.1 MQTT协议实现方式1.2.2 MQTT协议中的方法1.3 消息服务质量QoS1.3.1 消息服务质量QoS三个等级1.3.2 发布与订阅QoS1.4 Topic通配符匹配规则2. EMQX2.1 EMQ…

100183. 最大好子数组和

题目&#xff1a; 给你一个长度为 n 的数组 nums 和一个 正 整数 k 。 如果 nums 的一个子数组中&#xff0c;第一个元素和最后一个元素 差的绝对值恰好 为 k &#xff0c;我们称这个子数组为 好 的。换句话说&#xff0c;如果子数组 nums[i..j] 满足 |nums[i] - nums[j]| k…

Python基于时间序列分析的大气污染预测的设计与实现,附源码

1 简介 Python基于时间序列分析的大气污染预测的设计与实现 摘要&#xff1a;随着当今社会工业的发展&#xff0c;世界各地的空气质量都下降的非常明显&#xff0c;大气的污染对人们的身体健康会产生极大的危害。所以从20世纪初我国就十分关注空气质量的治理问题&#xff0c;…

【Java】Redis入门

1. Redis入门 1.1 Redis简介 Redis是一个基于内存的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件。 官网&#xff1a;https://redis.io 中文网&#xff1a;https://www.redis.net.cn/ key-value结构存储&#xff1a; 主要特点&#xff1a; 基于内…

【Android】使用Termux终端的SSH服务与电脑传输文件

在Android手机上有一个Termux APP&#xff0c;可运行类似 Linux 终端的模拟器&#xff0c;记得之前有讲过用电脑远程控制手机终端命令&#xff0c;那现在&#xff0c;怎样实现电脑与手机直接传输文件呢&#xff0c;且看这篇文章。 文章目录 Termux安装功能ssh服务从远程下载从本…

深度学习入门笔记(六)线性回归模型

本节&#xff0c;我们用线性回归为例子&#xff0c;回顾一些基本概念 6.1 相关性 相关性的取值范围是-1 到 1&#xff0c;越接近 1 或者-1 代表越相关&#xff0c;越接近 0 则越不相关。相关系数大于 0 称为正相关&#xff0c;小于 0 称为负相关。 假如 A 与 B 正相关&#…

[Linux 进程控制(二)] 写时拷贝 - 进程终止

文章目录 1、写时拷贝2、进程终止2.1 进程退出场景2.1.1 退出码2.1.2 错误码错误码 vs 退出码2.1.3 代码异常终止引入 2.2 进程常见退出方法2.2.1 exit函数2.2.2 _exit函数 本片我们主要来讲进程控制&#xff0c;讲之前我们先把写时拷贝理清&#xff0c;然后再开始讲进程控制。…

VMware无法检测到插入的USB设备,虚拟机插拔USB无反应

原本正常使用的VMware虚拟机&#xff0c;在进行了重装软件后&#xff0c;发现虚拟机插拔USB设备都无法检测到&#xff0c;没有任何的反应和提示。 通过一系列的操作发现&#xff0c;在新安装了VMware workstation 软件后&#xff0c;存在一定的概率性会发生VMware虚拟机无法自…

牛客网-------------------------长方体

解题思路&#xff1a; 设棱长为x&#xff0c;y&#xff0c;z&#xff1b;假设已知面积为a&#xff0c;b&#xff0c;c 那么&#xff0c;xya&#xff1b;yzb&#xff1b;xzc&#xff1b; 一式除二式得x/za/b x(a/b)*z 联立xzc 代入得&#xff08;a/b)z^2c z^2c*b/a z根号下&…

pdf怎么转成高清图?pdf在线转换器推荐分享

在日常的工作或者学习中&#xff0c;有时候会需要将编辑好的pdf转高清图片&#xff0c;这样更方便我们后续使用&#xff0c;那么怎么将pdf转图片&#xff08;https://www.yasuotu.com/pdftopic&#xff09;还能保持清晰呢&#xff1f;下面介绍一款pdf转换工具&#xff0c;支持p…

某头部股份制银行基于 Data Fabric 的敏捷数据准备创新实践

【背景】 随着数字化转型的持续深入&#xff0c;某头部股份制银行把“依托数据洞察提升管理和营销的精准度、实现经营与服务的精细化与个性化”作为参与下一阶段数字化业务竞争的核动力。经过多年的探索&#xff0c;该头部股份制银行数字化技术与业务场景的融合逐渐进入了深水…

中科大计网学习记录笔记(三):接入网和物理媒体

前言&#xff1a; 学习视频&#xff1a;中科大郑烇、杨坚全套《计算机网络&#xff08;自顶向下方法 第7版&#xff0c;James F.Kurose&#xff0c;Keith W.Ross&#xff09;》课程 该视频是B站非常著名的计网学习视频&#xff0c;但相信很多朋友和我一样在听完前面的部分发现信…

《学成在线》微服务实战项目实操笔记系列(P1~P49)【上】

《学成在线》项目实操笔记系列【上】&#xff0c;跟视频的每一P对应&#xff0c;全系列12万字&#xff0c;涵盖详细步骤与问题的解决方案。如果你操作到某一步卡壳&#xff0c;参考这篇&#xff0c;相信会带给你极大启发。同时也欢迎大家提问与讨论&#xff0c;我会尽力帮大家解…

高通android设备themal读取cpu温度

以msm8953的themal分布信息&#xff0c;主要是下图的位置&#xff1a; 这其中 cpu相关的themal的位置有&#xff1a; 读取thermal 温度数据可以通过以下几个步骤&#xff1a; 获取sensor_info rootmsm8953_64:/ # cat /sys/module/msm_thermal/sensor_info tsens:tsens_tz_se…

数据结构 - 线段树

1. 预制值&#xff1a; 构建的数组为&#xff0c;nums&#xff1a;【2&#xff0c; 5&#xff0c; 1&#xff0c; 4&#xff0c; 3】区间和问题&#xff0c;假设求区间 [1&#xff0c;3] 的和 2. 建树 2.1 构建线段树数组 int[] segT new int[4*n]&#xff08;为什么数组大…

Vivado Tri-MAC IP的例化配置(三速以太网IP)

目录 1 Tri-MAC IP使用RGMII接口的例化配置1.1 Data Rate1.2 interface配置1.3 Shared Logic配置1.4 Features 2 配置完成IP例化视图 1 Tri-MAC IP使用RGMII接口的例化配置 在网络设计中&#xff0c;使用的IP核一般为三速以太网IP核&#xff0c;使用时在大多数场景下为配置为三…

【计算机图形学】实验一 DDA算法、Bresenham算法

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

【Git】05 分离头指针

文章目录 一、分离头指针二、创建分支三、比较commit内容四、总结 一、分离头指针 正常情况下&#xff0c;在通过git checkout命令切换分支时&#xff0c;在命令后面跟着的是分支名&#xff08;例如master、temp等&#xff09;或分支名对应commit的哈希值。 非正常情况下&…

python_蓝桥杯刷题记录_笔记_全AC代码_入门3

前言 记录我的解法以及笔记思路&#xff0c;谢谢观看。 题单目录 1.P2141 [NOIP2014 普及组] 珠心算测验 2.P1567 统计天数 3.P1055 [NOIP2008 普及组] ISBN 号码 4.P1200 [USACO1.1] 你的飞碟在这儿 Your Ride Is Here 5.P1308 [NOIP2011 普及组] 统计单词数 6.P1047 […

Kubernetes的有状态应用示例:用PV部署WordPress和MySQL

文章目录 环境PVC和PV创建PVC和PV 创建kustomization.yaml添加secret生成器 为MySQL和WordPress添加资源配置部署和验证清理参考 环境 RHEL 9.3Docker Community 24.0.7minikube v1.32.0 PVC和PV PersistentVolume&#xff08;PV&#xff09;是在集群里由管理员手动provisio…