当前位置: 首页 > news >正文

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 函数的作用是在渲染的过程中&#xff0c;比较新旧节点的变化&#xff0c;通过打补丁的形式&#xff0c;进行新增、删除、移动或替换操作&#xff0c;此过程避免了大量的 dom 操作&#xff0c;提升了运行的性能。 patch 执行流程 patch 函数整体…...

蓝桥杯2024/1/28----十二届省赛题笔记

题目要求&#xff1a; 2、 竞赛板配置要求 2.1将 IAP15F2K61S2 单片机内部振荡器频率设定为 12MHz。 2.2键盘工作模式跳线 J5 配置为 KBD 键盘模式。 2.3扩展方式跳线 J13 配置为 IO 模式。 2.4 请注意 &#xff1a; 选手需严格按照以上要求配置竞赛板&#xff0c;编写和调…...

STM32+ESP8266 实现物联网设备节点

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

免费的ChatGPT网站(7个)

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

Go语言基础之单元测试

1.go test工具 Go语言中的测试依赖go test命令。编写测试代码和编写普通的Go代码过程是类似的&#xff0c;并不需要学习新的语法、规则或工具。 go test命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内&#xff0c;所有以_test.go为后缀名的源代码文件都是go …...

C++ easyX小程序(介绍几个函数的使用)

本小程序通过代码和注释&#xff0c;介绍了easyX窗口及控制台窗口的设置方法&#xff1b;还介绍了easyX中关于颜色、线型、画圆、画方、显示文字以及鼠标消息处理等函数的使用方法。为便于理解&#xff0c;本程序同时使用控制台和easyX窗口&#xff0c;由控制台控制程序运行、由…...

配置nginx以成功代理websocket

配置nginx以成功代理websocket 在使用socket.io的时候遇到这样一个问题&#xff1a;websocket接收的消息的顺序错位了&#xff0c;然后看了一下浏览器的console的报错&#xff0c;提示连接到ws失败&#xff0c;然后在浏览器的开发者工具的网络中看了一下ws对应的消息里面报错&…...

代码随想录算法训练营第二十二天|235.二叉搜索树的最近公共祖先、701.二叉搜索树中的插入操作、450.删除二叉搜索树中的节点

文档讲解&#xff1a; BST&#xff0c;各种插入删除操作 235.二叉搜索树的最近公共祖先 思路&#xff1a;昨天练习了二叉树的搜索&#xff0c;今天这道题是二叉搜索树的搜索&#xff0c;其具有有序这个特点&#xff0c;其能决定我们每次搜索是进入该节点的左子树还是右子树&…...

collection、ofType、select的联合用法(Mybatis实现树状结构查询)

需求 得到树结构数据也可以用lambda表达式也行&#xff0c;也可以直接循环递归也行&#xff0c;本文采用的是直接在Mybatis层得到结果&#xff0c;各有各的优势。 代码 1、实体类 Data public class CourseChapterVO implements Serializable {private static final long s…...

FLUENT Meshing Watertight Geometry工作流入门 - 4 局部加密区域

本视频中学到的内容&#xff1a; 使用Watertight Geometry Workflow 的 Create Local Refinement Regions 任务来创建细化的网格区域 视频链接&#xff1a; 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-贪吃蛇游戏

贪吃蛇游戏虽然很多&#xff0c;不过它可以作为软件创作的开端&#xff0c;用python来实现&#xff0c;然后dist成windows系统可执行文件。 import pygame import sys import random# 初始化 pygame.init()# 游戏设置 width, height 640, 480 cell_size 20 snake_speed 15# …...

应用案例 | 基于三维机器视觉的汽车副车架在线测量解决方案

在汽车制造领域中&#xff0c;精确的测量是确保产品质量和生产效率的关键。随着科技的不断进步&#xff0c;测量技术也在不断精进。 副车架是汽车底盘的重要组成部分&#xff0c;负责支撑引擎&#xff0c;是车辆结构中至关重要的组成部分之一&#xff0c;其制造质量直接关系到汽…...

线程的创建和使用threading.Thread()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 线程的创建和使用 threading.Thread() [太阳]选择题 关于以下代码的输出是&#xff1f; import threading import time def f(name): print(name) for i in range(3): print…...

大数据学习之Redis,十大数据类型的具体应用(四)

3.8 Redis基数统计&#xff08;HyperLogLog&#xff09; 需求 统计某个网站的UV、统计某个文章的UV 什么是UV unique Visitor &#xff0c;独立访客&#xff0c;一般理解为客户端IP 大规模的防止作弊&#xff0c;需要去重复统计独立访客 比如IP同样就认为是同一个客户 需要去…...

哪个牌子的头戴式耳机好?推荐性价比高的头戴式耳机品牌

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

Java EE 5 SDK架构

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

nop-entropy可逆计算入门(1)

第1步&#xff1a;从大佬的gitee&#xff1a;https://gitee.com/canonical-entropy/nop-entropy下载源码&#xff0c;进行本地编译&#xff0c;具体编译看项目下的readme,想偷懒的可以下载我编译后的jar&#xff0c;放到自己的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…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化

缓存架构 代码结构 代码详情 功能点&#xff1a; 多级缓存&#xff0c;先查本地缓存&#xff0c;再查Redis&#xff0c;最后才查数据库热点数据重建逻辑使用分布式锁&#xff0c;二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

并发编程 - go版

1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程&#xff0c;系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...

宇树科技,改名了!

提到国内具身智能和机器人领域的代表企业&#xff0c;那宇树科技&#xff08;Unitree&#xff09;必须名列其榜。 最近&#xff0c;宇树科技的一项新变动消息在业界引发了不少关注和讨论&#xff0c;即&#xff1a; 宇树向其合作伙伴发布了一封公司名称变更函称&#xff0c;因…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...