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…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...