vue3 源码解析(5)— patch 函数源码的实现
什么是 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.parentNodeif (parent) {parent.removeChild(n1.el) // 删除元素}n1 = null // 删除之后重新加载}// 通过节点的shapeFlag(描述该组件的类型)进行不同处理const { shapeFlag, type } = n2if (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])}}// 处理childrenif (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)) { // 已经是一个规范的 vnodereturn 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)}}// 比对childrenpatchChildren(n1, n2, el)
}const patchChildren = (n1, n2, el) => {const c1 = n1.childrenconst c2 = n2.childrenconst prevShapeFlag = n1.shapeFlagconst nextShapeFlag = n2.shapeFlagif (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.propsinstance.slots = n2.slots// 触发 beforeUpdate 生命周期钩子函数callBeforeUpdateHooks(instance)// 执行组件的更新操作instance.update()// 触发 updated 生命周期钩子函数callUpdatedHooks(instance)}
}
-
shouldUpdateComponent: 函数判断是否需要更新组件。该函数会比较新旧节点的属性、事件等是否发生了变化,以确定是否需要进行组件的更新操作。如果判断为需要更新组件,则执行下面的操作;否则,不进行任何更新操作。
-
callBeforeUpdateHooks: 函数触发组件的 beforeUpdate 生命周期钩子函数。该函数会在组件更新之前执行一些预处理或清理操作。
-
update: 执行组件的更新操作,包括重新渲染组件。
-
callUpdatedHooks: 函数触发组件的 updated 生命周期钩子函数。该函数会在组件更新完成后执行一些操作,如更新后的 DOM 操作、发送请求等。
总结
为了便于理解我把以上代码流程流程图的形式展示出来了,希望通过本篇文章可以帮助读者更好的了解 vue 的渲染过程。
相关文章:

vue3 源码解析(5)— patch 函数源码的实现
什么是 patch 在 vue 中 patch 函数的作用是在渲染的过程中,比较新旧节点的变化,通过打补丁的形式,进行新增、删除、移动或替换操作,此过程避免了大量的 dom 操作,提升了运行的性能。 patch 执行流程 patch 函数整体…...

蓝桥杯2024/1/28----十二届省赛题笔记
题目要求: 2、 竞赛板配置要求 2.1将 IAP15F2K61S2 单片机内部振荡器频率设定为 12MHz。 2.2键盘工作模式跳线 J5 配置为 KBD 键盘模式。 2.3扩展方式跳线 J13 配置为 IO 模式。 2.4 请注意 : 选手需严格按照以上要求配置竞赛板,编写和调…...

STM32+ESP8266 实现物联网设备节点
目录 一、硬件准备 二、编译环境 三、源代码地址 四、说明 五、测试方法 六、所有测试工具和文档 本项目使用stm32F103ZEesp8266实现一个物联网的通信节点,目前支持的协议有mqtt,tcp。后续会持续更新,增加JSON,传感器&#…...

免费的ChatGPT网站(7个)
还在为找免费的chatGPT网站或者应用而烦恼吗?博主归纳总结了7个国内非常好用,而且免费的chatGPT网站,AI语言大模型,我们都来接触一下吧。 免费!免费!免费!...,建议收藏保存。 1&…...

Go语言基础之单元测试
1.go test工具 Go语言中的测试依赖go test命令。编写测试代码和编写普通的Go代码过程是类似的,并不需要学习新的语法、规则或工具。 go test命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内,所有以_test.go为后缀名的源代码文件都是go …...
C++ easyX小程序(介绍几个函数的使用)
本小程序通过代码和注释,介绍了easyX窗口及控制台窗口的设置方法;还介绍了easyX中关于颜色、线型、画圆、画方、显示文字以及鼠标消息处理等函数的使用方法。为便于理解,本程序同时使用控制台和easyX窗口,由控制台控制程序运行、由…...
配置nginx以成功代理websocket
配置nginx以成功代理websocket 在使用socket.io的时候遇到这样一个问题:websocket接收的消息的顺序错位了,然后看了一下浏览器的console的报错,提示连接到ws失败,然后在浏览器的开发者工具的网络中看了一下ws对应的消息里面报错&…...
代码随想录算法训练营第二十二天|235.二叉搜索树的最近公共祖先、701.二叉搜索树中的插入操作、450.删除二叉搜索树中的节点
文档讲解: BST,各种插入删除操作 235.二叉搜索树的最近公共祖先 思路:昨天练习了二叉树的搜索,今天这道题是二叉搜索树的搜索,其具有有序这个特点,其能决定我们每次搜索是进入该节点的左子树还是右子树&…...
collection、ofType、select的联合用法(Mybatis实现树状结构查询)
需求 得到树结构数据也可以用lambda表达式也行,也可以直接循环递归也行,本文采用的是直接在Mybatis层得到结果,各有各的优势。 代码 1、实体类 Data public class CourseChapterVO implements Serializable {private static final long s…...

FLUENT Meshing Watertight Geometry工作流入门 - 4 局部加密区域
本视频中学到的内容: 使用Watertight Geometry Workflow 的 Create Local Refinement Regions 任务来创建细化的网格区域 视频链接: FLUENT Meshing入门教程-4创建局部加密区域_哔哩哔哩_bilibili 可以通过使用 Watertight Geometry Workflow 的 Create…...

前端添加富文本/Web 富文本编辑器wangeditor
官网wangEditor 需要引入两个文件 <link href"https://unpkg.com/wangeditor/editorlatest/dist/css/style.css" rel"stylesheet"> <script src"https://unpkg.com/wangeditor/editorlatest/dist/index.js"></script> 前端…...
软件价值2-贪吃蛇游戏
贪吃蛇游戏虽然很多,不过它可以作为软件创作的开端,用python来实现,然后dist成windows系统可执行文件。 import pygame import sys import random# 初始化 pygame.init()# 游戏设置 width, height 640, 480 cell_size 20 snake_speed 15# …...

应用案例 | 基于三维机器视觉的汽车副车架在线测量解决方案
在汽车制造领域中,精确的测量是确保产品质量和生产效率的关键。随着科技的不断进步,测量技术也在不断精进。 副车架是汽车底盘的重要组成部分,负责支撑引擎,是车辆结构中至关重要的组成部分之一,其制造质量直接关系到汽…...

线程的创建和使用threading.Thread()
【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 线程的创建和使用 threading.Thread() [太阳]选择题 关于以下代码的输出是? import threading import time def f(name): print(name) for i in range(3): print…...
大数据学习之Redis,十大数据类型的具体应用(四)
3.8 Redis基数统计(HyperLogLog) 需求 统计某个网站的UV、统计某个文章的UV 什么是UV unique Visitor ,独立访客,一般理解为客户端IP 大规模的防止作弊,需要去重复统计独立访客 比如IP同样就认为是同一个客户 需要去…...

哪个牌子的头戴式耳机好?推荐性价比高的头戴式耳机品牌
随着科技的不断发展,耳机市场也呈现出百花齐放的态势,从高端的奢侈品牌到亲民的平价品牌,各种款式、功能的耳机层出不穷,而头戴式耳机作为其中的一员,凭借其优秀的音质和降噪功能,受到了广大用户的喜爱&…...

Java EE 5 SDK架构
Java EE 5 SDK架构 大型组织每天都要处理大量数据和多用户的相关事务。为管理该组织如此大型而又复杂的系统,开发了企业应用程序。企业应用程序是在服务器上托管的应用程序,通过计算机网络同时向大量用户提供服务。这种应用程序可采用各种技术开发,如Java EE 5。Java EE 5平…...

nop-entropy可逆计算入门(1)
第1步:从大佬的gitee:https://gitee.com/canonical-entropy/nop-entropy下载源码,进行本地编译,具体编译看项目下的readme,想偷懒的可以下载我编译后的jar,放到自己的maven仓库 https://pan.baidu.com/s/15qANnrCh5RV…...

C++(9) 虚函数
文章目录 虚函数1. 虚函数1.1 虚函数案例11.2 虚函数案例21.2 纯虚函数1.3 纯虚函数语法要求总环1.4 纯虚函数应用1.4.1 生活案例1.4.2 虚函数引用代码 虚函数 1. 虚函数 1.1 虚函数案例1 #include <iostream>using namespace std;class Animal { public:// Animal 类…...

uniapp 使用canvas 画海报,有手粘贴即可用(拆成组件了,看后面)
1.直接使用 html部分 <view click"doposter">下载海报</view> <canvas canvas-id"myCanvas" type2d style"width: 370px; height: 550px;opcity:0;position: fixed;z-index:-1;" id"myCanvas" />js 部分 drawBac…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...

ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...

【Linux】自动化构建-Make/Makefile
前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具:make/makfile 1.背景 在一个工程中源文件不计其数,其按类型、功能、模块分别放在若干个目录中,mak…...
SQL Server 触发器调用存储过程实现发送 HTTP 请求
文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter
java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用(Math::max) 2 函数接口…...