【React学习】React更新渲染原理

news2025/1/11 14:08:37

当我们调用 setState 之后发生了什么?react经历了怎样的过程将新的 state 渲染到页面上?

一次react更新,核心就是对虚拟dom进行diff,找出最少的需要变化的dom节点,然后对其进行相应的dom操作,用户即可在页面上看到更新。但 react 作为广泛使用的框架,需要考虑更多的因素,考虑多个更新的优先级,考虑主线程占用时长,考虑diff算法复杂度,考虑性能。。等等,本文就来探讨一下react在其内部是如何处理数据更新的。

react在内部使用fiber这种数据结构来作为虚拟dom【react16+】,它与dom tree一一对应,形成fiber tree,一次react更新,本质是fiber tree结构的更新变化。而fiber tree结构的更新,用更专业的术语来讲,其实就是fiber tree的协调(Reconcile)。Reconcile中文意思是调和、使一致,协调fiber tree,就是调整fiber tree的结构,使其和更新后的jsx模版结构、dom tree保持一致。

react从16起,将更新机制分为三个模块,也可以说是三个步骤,分别是Schedule【调度】、Reconcile【协调】、render【渲染】

Schedule

为什么需要Schedule?

首先我们要知道react在进行协调时,提供了两种模式:Legacy mode 同步阻塞模式和 Concurrent mode 并行模式。

不同上下文中的更新会触发不同的模式,如果是在 eventsetTimeoutnetwork requestcallback 中触发更新,react 会采用 Legacy 模式。如果更新与 SuspenseuseTransitionOffScreen 相关,那么 react 会采用 Concurrent 模式。

Legacy mode

Legacy mode在协调时会启动 workLoopSyncworkLoopSync 开始工作以后,要等到所有 fiber node 都处理完毕以后,才会结束工作,也就是 fiber tree 的协调过程不可中断。

Legacy mode存在的问题:如果 fiber tree 的结构很复杂,那么协调 fiber tree 可能会占用大量的时间,导致主线程会一直被 js 引擎占用,渲染引擎无法在规定时间(浏览器刷新频率 - 16.7ms)内完成工作,使得页面出现卡顿(掉帧),影响用户体验。

Concurrent mode

鉴于Legacy mode存在的问题,react团队在react 16中提出了 Concurrent mode的概念,并在react 18中开放使用。react16、17一直为此做准备。

Concurrent 模式最大的意义在于,使用Concurrent 模式以后的react的应用可以做到:

  • 协调可以中断、恢复;不会长时间阻塞浏览器渲染
  • 高优先级更新可以中断低优先级更新,优先渲染

那么,怎么做到这两点呢?

事实上,Schedule就是用来完成这个任务的,调度任务的优先级,使高优先级任务优先进入Reconcile,并且提供中断和恢复机制。

时间切片

react采用时间切片的方式来实现协调的中断和恢复,Concurrent mode在协调时会启动 workLoopConcurrentworkLoopConcurrent 开始工作以后,每次协调 fiber node 时,都会判断当前时间片是否到期。如果时间片到期,会停止当前 workLoopConcurrent,让出主线程,然后请求下一个时间片继续协调。

协调的中断及恢复,类似于浏览器的eventloop,js引擎和渲染引擎互斥,在主线程中交替工作。

我们可以通过模拟 eventLoop来实现时间分片以及重新请求时间片。一段 js 程序,如果在规定时间内没有结束,那我们可以主动结束它,然后请求一个新的时间片,在下一个时间片内继续处理上一次没有结束的任务。

let taskQueue = [];   // 任务列表
let shouldTimeEnd = 5ms;   // 一个时间片定义为 5ms
let channel = new MessageChannel();  // 创建一个 MessageChannel 实例
​
function wookLoop() {
    let beginTime = performance.now();  // 记录开始时间
    while(true) { // 循环处理 taskQueue 中的任务
        let currentTime = performance.now();  // 记录下一个任务开始时的时间
        if (currentTime - beginTime >= shouldTimeEnd) break;  // 时间片已经到期,结束任务处理
        processTask();  // 时间片没有到期,继续处理任务
  }
    if (taskQueue.length) { // 时间片到期,通过调用 postMessage,请求下一个时间片
        channel.port2.postMessage(null); 
  }
}
​
channel.port1.onmessage = wookLoop;  // 在下一个时间片内继续处理任务
workLoop(); 

和浏览器的消息队列 一样, react 也会维护一个任务队列 taskQueue,然后通过 workLoop 遍历 taskQueue,依次处理 taskQueue 中的任务。

taskQueue 中收集任务是有先后处理顺序的,workLoop 每次处理 taskQueue 中的任务时,都会挑选优先级最高的任务进行处理。

每触发一次 react 更新,意味着一次 fiber tree 的协调,但协调并不会在更新触发时立刻同步进行。相反,react 会为这一次更新,生成一个 task,并添加到 taskQueue 中,fiber tree 的协调方法会作为新建 taskcallback。当 wookLoop 开始处理该 task 时,才会触发 taskcallback,开始 fiber tree 的协调。

任务的优先级

react在内部定义了 5 种类型的优先级,以及对应的超时时间timeout

  • ImmediatePriority, 直接优先级,对应用户的 clickinputfocus 等操作; timeout为 -1,表示任务要尽快处理;
  • UserBlockingPriority,用户阻塞优先级,对应用户的 mousemovescroll 等操作;timeout 为 250 ms;
  • NormalPriority,普通优先级,对应网络请求、useTransition 等操作; timeout 为 5000 ms;
  • LowPriority,低优先级(未找到应用场景);timeout 为 10000 ms;
  • IdlePriority,空闲优先级,如 OffScreen; timeout 为 1073741823 ms;

5 种优先级的顺序为: ImmediatePriority > UserBlockingPriority > NormalPriority > LowPriority > IdlePriority

在确定了任务的优先级以后,react 会根据优先级为任务计算一个过期时间 expirationTime,即 expirationTime = currentTime + timeout,然后根据 expirationTime 时间来决定任务处理的先后顺序。

expirationTime越小的任务会被排在task队列的越前面,之所以需要timeout,而不是直接对比优先级等级,是为了避免低优先级任务长时间被插队而导致一直无响应;同时,在时间分片到期时,需要根据expirationTime判断下一个要处理的任务是否过期,如果已过期,就不能让出主线程,需要立即处理。

⚠️注:react17中用Lanes重构了优先级算法,此处不展开陈述,有兴趣的同学可查阅相关文档。

获取最先处理的task

react 采用了小顶堆来存储task,实现最小优先队列,即 taskQueue 是一个小顶堆,放在堆顶的task是需要最先处理的。

使用最小堆时,有三个操作:pushpoppeek

  • push,入堆操作,即将 task 添加到 taskQueue 中。添加一个新创建的 task 时,会将 task 添加到最小堆的堆底,然后对最小堆做自底向上的调整。调整时,会比较堆节点(task) 的 expirationTime,将 expirationTime 较小的 task 向上调整。* peek,获取堆顶元素,即获取需要最先处理的 task,执行 taskcallback,开始 fiber tree 的协调。* pop,堆顶元素出堆,即 task 处理完毕,从 taskQueue 中移除。移除堆顶元素以后,会对最小堆做自顶向下的调整。调整时,也是比较堆节点(task) 的 expirationTime,将 expirationTime 较大的 task 向下调整。### 高优先级的更新中断低优先级的更新

Concurrent 模式下,如果在低优先级更新的协调过程中,有高优先级更新进来,那么高优先级更新会中断低优先级更新的协调过程。

每次拿到新的时间片以后,workLoopConcurrent 都会判断本次协调对应的优先级和上一次时间片到期中断的协调的优先级是否一样。如果一样,说明没有更高优先级的更新产生,可以继续上次未完成的协调;如果不一样,说明有更高优先级的更新进来,此时要清空之前已开始的协调过程,从根节点开始重新协调。等高优先级更新处理完成以后,再次从根节点开始处理低优先级更新。

Reconcile

前面说到,reconcile(协调)就是fiber tree 结构的更新,那么具体是怎样更新的呢?本小节就来解答这个问题。

前置知识

从jsx到dom

Step1: 从jsx生成react element

jsx 模板通过 babel 编译为 createElement 方法;执行组件方法,触发 createElement 的执行,返回 react element

Step2: 从react element生成fiber tree

  • fiber tree 中存在三种类型的指针 childsiblingreturn。其中,child 指向第一个子节点,sibling 指向兄弟节点,return 指针指向父节点;* fiber tree 采用的深度优先遍历,如果节点有子节点,先遍历子节点;子节点遍历结束以后,再遍历兄弟节点;没有子节点、兄弟节点,就返回父节点,遍历父节点的兄弟节点;* 当节点的 return 指针返回 null 时,fiber tree 的遍历结束;Step3: fiber tree生成之后,从fiber tree到真实dom,就是处理fiber tree上对应的副作用,包括:

  • 所有 dom 节点的新增;

  • componentDidMountuseEffectcallback 函数的触发;

  • ref 引用的初始化;

双缓存fiber tree

react 做更新处理时,会同时存在两颗 fiber tree。一颗是已经存在的 old fiber tree,对应当前屏幕显示的内容,称为 current fiber tree;另外一颗是更新过程中构建的 new fiber tree,称为 workInProgress fiber tree

current fiber treeworkInProgress fiber tree可以通过alternate指针互相访问

当更新完成以后,使用 workInProgress fiber tree 替换掉 current fiber tree,作为下一次更新的 current fiber tree

协调的过程

协调过程中主要做三件事情:

1.为 workInProgress fiber tree 生成 fiber node

2.为发生变化的 fiber node标记副作用 effect

3.收集带 effectfiber node

生成workInProgress fiber tree

workInProgress fiber tree 作为一颗新树,生成 fiber node 的方式有三种:

  • 克隆(浅拷贝) current fiber node,意味着原来的 dom 节点可以复用,只需要更新 dom 节点的属性,或者移动 dom 节点;
  • 新建一个 fiber node,意味着需要新增加一个 dom 节点;
  • 直接复用 current fiber node,表示对应的 dom 节点完全不用做任何处理;

复用的场景:当子组件的渲染方法(类组件的 render、函数组件方法)没有触发,(比如使用了React.memo),没有返回新的 react element,子节点就可以直接复用 current fiber node

在日常开发过程中,我们可以通过合理使用 ShouldComponentUpdateReact.memo,阻止不必要的组件重新 render,通过直接复用 current fiber node,加快 workInProgress fiber tree 的协调,达到优化的目的。

相反,只要组件的渲染方法被触发,返回新的 react element,那么就需要根据新的 react element 为子节点创建 fiber node(通过浅拷贝或新建)。

  • 如果能在 current fiber tree 中找到匹配节点,那么可以通过克隆(浅拷贝) current fiber node 的方式来创建新的节点;

  • 相反,如果无法在 current fiber tree 找到匹配节点,那么就需要重新创建一个新的节点;

我们常说的diff算法就是发生在这一环节。

diff算法比较的双方是 workInProgress fiber tree 中用于构建 fiber nodereact elementcurrent fiber tree 中的 fiber node,比较两者的 keytype,根据比较结果来决定如何为 workInProgress fiber tree 创建 fiber node

【 key 和 type 】:

key就是 jsx 模板中元素上的 key 属性。如果不写默认为undefinedjsx 模板转化为 react element 后,元素的 key 属性会作为 react elementkey 属性。同样的,react element 转化为 fiber node 以后,react elementkey 属性也会作为 fiber nodekey 属性。

jsx 中不同的元素类型,有不同的type

<Component name="xxxx" />  //type = Component, 是一个函数
<div></div>    // type = "div", 是一个字符串
<React.Fragment></React.Fragment>  // type = React.Fragment, 是一个数字(react 内部定义的); 

jsx 模板转化为 react element 以后,react elementtype 属性会根据 jsx 元素的类型赋不同的值,可能是组件函数,也可能是 dom 标签字符串,还可能是数字。 react element 转化为 fiber node 以后,react elementtype 属性也会作为 fiber nodetype 属性。

综上,判断拷贝 current fiber node 的逻辑,概括来就是:

reactElement.key === currentFiberNode.key && reactElement.type === currentFiberNode.type, current fiber node //可以克隆;

reactElement.key !== currentFiberNode.key, current fiber node //不可克隆;

reactElement.key === currentFiberNode.key && reactElement.type !== currentFiberNode.type, current fiber node //不可克隆; 

diff 算法:

  • 已匹配的父节点的直接子节点进行比较,不跨父节点比较;
  • 通过比较 keytype 来判断是否需要克隆 current fiber node。只有 keytype 都相等,才克隆 current fiber node 作为新的节点,否则就需要新建一个节点。key 值和节点类型typekey 的优先级更高。如果 key 值不相同,那么节点不可克隆。
  • 当比较 single react elementcurrent fiber node list 时,只需要遍历 current fiber node list,比较每个 current fiber nodereact elementkey 值和 type。只有 keytype 都相等,react elementcurrent fiber node 才能匹配。如果有匹配的,直接克隆current fiber node,作为 react element 对应的 workInProgress fiber node。如果没有匹配的 current fiber node,就需要为 react element 重新创建一个新的 fiber node 作为 workInProgress fiber node
  • 当比较react element listcurrent fiber node list 时,还需要通过列表下标 index 判断 wokrInProgress fiber node 是否相对于克隆的 current fiber node 发生了移动。这也是diff中最复杂的地方。

为发生变化的fiber node标记effect

判断节点是否发生变化:

  • 节点只要是重新创建的而不是克隆自 current fiber node,那么节点就百分之百发生了变化,需要更新;* 节点克隆自 current fiber node,需要比较 props 是否发生了变化,如果 props 发生了变化,节点需要更新;* 节点克隆自 current fiber node,且是组件类型,还需要比较 state 是否发生了变化,如果 state 发生了变化,节点需要更新;常见的effect类型:

  • Placement,放置,只针对 dom 类型的 fiber node,表示节点需要做移动或者添加操作。

  • Update,更新,针对所有类型的 fiber node,表示 fiber node 需要做更新操作。

  • PlacementAndUpdate,放置并更新,只针对 dom 类型的 fiber node,表示节点发生了移动且 props 发生了变化。

  • Ref,表示节点存在 ref,需要初始化 / 更新 ref.current

  • Deletion,删除,针对所有类型的 fiber node,表示 fiber node 需要移除。

  • Snapshot,快照,主要是针对类组件 fiber node。当类组件 fiber node 发生了 mount 或者 update 操作,且定义了 getSnapshotBeforeUpdate 方法,就会标记 Snapshot

  • Passive,主要针对函数组件 fiber node,表示函数组件使用了 useEffect。当函数组件节点发生 mount 或者 update 操作,且使用了 useEffect hook,就会给 fiber node 标记 Passive

  • Layout,主要针对函数组件 fiber node,表示函数组件使用了 useLayoutEffect。当函数组件节点发生 mount 或者 update 操作,且使用了 useLayoutEffect hook,就会给 fiber node 标记 Layout

react 使用二进制数来声明 effect,如 Placement 为 2 (0000 0010),Update 为 4 (0000 0100)。一个 fiber node 可同时标记多个 effect,如函数组件 props 发生变化且使用了 useEffect hook,那么就可以使用 Placement | Update = 516(位运算符) 来标记。

收集带 effectfiber node

如果一个 fiber node 被标记了 effect,那么 react 就会在这个 fiber node 完成协调以后,将这个 fiber node 收集到effectList中。当整颗 fiber tree 完成协调以后,所有被标记 effectfiber node 都被收集到一起。

收集fiber nodeeffectList 采用单链表结构存储,firstEffect 指向第一个标记 effectfiber nodelastEffect 标记最后一个 fiber node,节点之间通过 nextEffect 指针连接。

由于 fiber tree 协调时采用的顺序是深度优先,协调完成的顺序是子节点、子节点兄弟节点、父节点,所以收集带 effect 标记的 fiber node 时,顺序也是子节点、子节点兄弟节点、父节点。

Render

render也称为commit,是对协调过程中标记的effect 的处理

effect 的处理分为三个阶段,这三个阶段按照从前到后的顺序为:

1.before mutation 阶段 (dom 操作之前)

2.mutation 阶段 (dom 操作)

3.layout 阶段 (dom 操作之后)

不同的阶段,处理的 effect 种类也不相同。在每个阶段,react 都会从 effectList 链表的头部 - firstEffect 开始,按序遍历 fiber node, 直到 lastEffect

before mutation 阶段

before mutation 阶段的主要工作是处理带 Snapshot 标记的 fiber node。 从 firstEffect 开始遍历 effect 列表,如果 fiber nodeSnapshot 标记,触发 getSnapshotBeforeUpdate 方法。

mutation 阶段

mutation 阶段的主要工作是处理带 DeletionPlacementPlacementAndUpdateUpdate 标记的 fiber node。 在这一阶段,涉及到 dom 节点的更新、新增、移动、删除,组件节点删除导致的 componentWillUnmountdestory 方法的触发,以及删除节点引发的 ref 引用的重置。

dom 节点的更新:

  • 通过原生的 API setAttributeremoveArrribute 修改 dom 节点的 attr
  • 直接修改 dom 节点的 style
  • 直接修改 dom 节点的 innerHtmltextContent

dom 节点的新增和移动:

  • 如果新增(移动)的节点是父节点的最后一个子节点,那么可以直接使用 appendChild 方法。

  • 如果不是最后一个节点,需要使用 insertBefore 方法。通过遍历找到第一个没有带Placement标记的节点作为insertBefore的定位元素。

dom节点的删除:

  • 如果节点是 dom 节点,通过 removeChild 移除;
  • 如果节点是组件节点,触发 componentWillUnmountuseEffectdestory 方法的执行;
  • 如果标记 Deletion 的节点的子节点中有组件节点,深度优先遍历子节点,依次触发子节点的 componentWillUnmountuseEffectdestory 方法的执行;
  • 如果标记 Deletion 的节点及子节点关联了 ref 引用,要将 ref 引用置空,及 ref.current = null(也是深度优先遍历);

layout 阶段

layout 阶段的主要工作是处理带 update 标记的组件节点和带 ref 标记的所有节点。 工作内容如下:

  • 如果类组件节点是mount操作,触发 componentDidMount;如果是 update 操作,触发 componentDidUpdate
  • 如果函数组件节点时 mount 操作,触发 useLayoutEffectcallback;如果是 update 操作,先触发上一次更新生成的 destory,再触发这一次的 callback
  • 异步调度函数组件的 useEffect
  • 如果组件节点关联了 ref 引用,要初始化 ref.current;

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

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

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

相关文章

亲子游小程序开发,助力商家掌握亲子流量

随着新生代父母越来越年轻化&#xff0c;育儿观念上也发生了很大改变&#xff0c;当代父母已经不在单纯的关注孩子的学习情况&#xff0c;学习固然重要但是孩子的身心健康也是非常重要的。所以很多父母开始带着自己的孩子参加各种各样的亲子活动&#xff0c;多参加亲子活动可以…

Python数据分析之Pandas库

Python数据分析之Pandas库一、Pandas简介二、Pandas库的安装三、Pandas的数据结构四、Series 和 DataFrame 数据结构的使用五、其他可以参考的网站一、Pandas简介 Pandas是python的一个数据分析包&#xff0c;最初由AQR Capital Management于2008年4月开发&#xff0c;并于200…

Struts2框架中的Action接口和ActionSupport类

Struts2框架中的Action接口和ActionSupport类1、Action接口2、ActionSupport类3、登录案例3.1、页面3.2、控制器3.3、struts.xml配置3.4、测试1、Action接口 Action是Struts2框架的核心&#xff0c;因为它们适用于任何MVC&#xff08;Model View Controller&#xff09;框架。…

【Maven基础】单一架构案例(二)

第三节 搭建环境&#xff1a;事务控制 1、总体思路 2、TransactionFilter 2.1、创建 Filter 类 2.2、TransactionFilter 完整代码 public class TransactionFilter implements Filter {// 声明集合保存静态资源扩展名private static Set<String> staticResourceExtNam…

长时间序列遥感数据处理及在全球变化、物候提取、植被变绿与固碳分析、生物量估算与趋势分析等领域中的实践技术应用

【查看原文】长时间序列遥感数据处理及在全球变化、物候提取、植被变绿与固碳分析、生物量估算与趋势分析等领域中的实践技术应用 植被是陆地生态系统中最重要的组分之一&#xff0c;也是对气候变化最敏感的组分&#xff0c;其在全球变化过程中起着重要作用&#xff0c;能够指…

云知声: 基于 JuiceFS 的超算平台存储实践

云知声从一家专注于语音及语言处理的技术公司&#xff0c;现在技术栈已经发展到具备图像、自然语言处理、信号等全栈式的 AI 能力&#xff0c;是国内头部人工智能独角兽企业。公司拥抱云计算&#xff0c;在智慧医疗、智慧酒店、智慧教育等方面都有相应的解决方案。 Atlas 是云知…

技术栈入门------RabbitMQ

Direct交换机是路由键精准匹配 Fanout交换机是不看路由键 &#xff0c;只要你消息发给了某个交换机&#xff0c;这个交换机就立马把消息转给绑定了这个交换机的所有队列&#xff0c;所以速度最快 Topic交换机可以把一个消息根据交换机和消息队列的绑定的路由键进行匹配&#xf…

没有二十年功力,写不出Thread.sleep(0)这一行“看似无用”的代码!

你好呀&#xff0c;我是喜提七天居家隔离的歪歪。 这篇文章要从一个奇怪的注释说起&#xff0c;就是下面这张图&#xff1a; 我们可以不用管具体的代码逻辑&#xff0c;只是单单看这个 for 循环。 在循环里面&#xff0c;专门有个变量 j&#xff0c;来记录当前循环次数。 第…

项目实战之旅游网(一)项目介绍 项目搭建

目录 一.项目介绍 1.功能介绍 2.技术选型 3.实体类 二.项目搭建 1.创建项目 2.adminLTE 3.编写后端首页 4.提取统一后台模板 5.重构后台首页 ************************************************************************* 项目代码地址&#xff1a;等写完这个项目我…

jsoup

1.什么是jsoup jsoup&#xff1a;Java HTML解析器&#xff0c;专为HTML编辑&#xff0c;清理&#xff0c;抓取和XSS安全而构建 2.依赖 <dependency><!-- jsoup HTML parser library https://jsoup.org/ --><groupId>org.jsoup</groupId><artifac…

干货!深入学习必学的模型微调

学习目标 知道微调的原理能够利用微调模型来完成图像的分类任务1.微调 如何在只有6万张图像的MNIST训练数据集上训练模型。学术界当下使用最广泛的大规模图像数据集ImageNet&#xff0c;它有超过1,000万的图像和1,000类的物体。然而&#xff0c;我们平常接触到数据集的规模通…

浅析JWT

Cookie-session 我们都知道JWT一般用于用户登录等需要记住的操作&#xff0c;在谈论JWT之前就不得不谈谈以前的cookie-session登录了 。因为http协议是一种无状态协议&#xff0c;即每次服务端接收到客户端的请求时&#xff0c;都是一个全新的请求&#xff0c;服务器并不知道客…

【从零开始学微服务】08.引入微服务架构的时机

大家好&#xff0c;欢迎来到万猫学社&#xff0c;跟我一起学&#xff0c;你也能成为微服务专家。 在了解引入微服务架构的时机之前&#xff0c;架构设计时一般需要遵循的三个原则。 架构设计三个原则 架构设计一般需要遵循以下三个原则&#xff1a; 合适原则&#xff1a;合适…

NeurIPS'22 | APG:面向CTR预估的自适应参数生成网络

丨目录&#xff1a; 摘要 背景 Method 实验 结语▐ 摘要目前基于深度学习的CTR预估模型&#xff08;即 Deep CTR Models&#xff09;被广泛的应用于各个应用中。传统的 Deep CTR Models 的学习模式是相对静态的&#xff0c;即所有的样本共享相同的网络参数。然而&#xff0c;由…

IntelliJ IDEA中我最爱的10个快捷操作

1. psvm/main快速生成 main() 方法 在日常开发中&#xff0c;我们经常需要写main()方法&#xff0c;这时候您也可以使用main或者psvm命令快速地帮助我们创建出main()方法。 2.sout快速生成println()方法 打印输出一些内容到控制台也是频率很高的一个行为&#xff0c;我们可以…

Pytest断言

&#x1f534;pytest 允许使用标准的python assert 用于验证Python测试中的期望和值。所以并不像unittest的那么丰富。但是我们可以重写。 ❞小例子--介绍 import pytestclass Testnew:def test_num(self):assert 1 "1"def test_dic(self):assert {"QA":…

MySql索引下推知识分享

作者&#xff1a;刘邓忠 Mysql 是大家最常用的数据库&#xff0c;下面为大家带来 mysql 索引下推知识点的分享&#xff0c;以便巩固 mysql 基础知识&#xff0c;如有错误&#xff0c;还请各位大佬们指正。 1 什么是索引下推 索引下推 (Index Condition Pushdown&#xff0c;…

技术分享 | 测试的本质是什么?

本文将分别浅谈不同阶段的业务、不同端的业务、不同类型的业务的测试差异&#xff0c;再抽离其中的测试目标/本质。仅为笔者个人观点&#xff0c;欢迎批评指正。 一、不同阶段业务对测试的需求不同 不同阶段业务对测试的需求不同。这点几乎经历过的人员都心有戚戚焉。 从0到1的…

盘点导致Spring事务失效的4个场景

1&#xff0c;非运行时异常导致事务无法回滚 我们知道&#xff0c;Spring是通过AOP的方式来实现事务的&#xff0c;而在处理事务的过程中&#xff0c;Spring只有捕获到RuntimeException或者Error的时候才会触发回滚操作&#xff0c;如果我们在代码中抛出的是非运行时异常&…

Web前端学习之虚拟DOM如何进化为真实DOM

Vue和React的Render函数中都涉及到了Virtual DOM的概念&#xff0c;Virtual DOM也是性能优化上的重要一环&#xff0c;同时突破了直接操作真实DOM的瓶颈&#xff0c;本文带着以下几个问题来阐述Virtual DOM。 1.为什么要操作虚拟 DOM? 2.什么是虚拟 DOM? 3.手把手教你实现…