React 使用链表遍历组件树
React 为在有限的资源情况下,更好地控制UI的更新,提出了时间分片的概念。以达到三个目标:
performing non-blocking rendering(无阻塞渲染);applying updates based on the priority(根据优先级渲染);pre-rendering content in the background(预渲染)。
React 遍历Dom Tree:
1. updates state and props(更新状态和属性)
2. calls lifecycle hooks(执行生命周期回调函数)
3. retrieves the children from the component(找到子组件)
4. compares them to the previous children (对子组件进行diff)
5. figures out the dom updates the need to be performed(找出变更,执行dom操作)
when dealing with UIs, the problem is that if too much work is executed all at once ,it can cause animations to drap frames。(当在有太多内容要更新时,会出现掉帧的现象,即卡顿,更新跟不上浏览器的刷新速度)
if react is going to walk the entire tree of components synchromously and perform work for each component ,it may run over 16ms available for an application code to execute its logic 。And this will cause frames to drop causing stuttering visual effects.
在理解react如何通过时间分片的来实现对用户而言的无阻塞渲染之前,需要了解两个浏览器的api:
requestIdleCallback会在某一帧结束后的空余时间或者用户处于不活跃的的状态时,处理我们的工作。利用这个API能使我们尽可能高效利用任何空闲的资源。
requestAnimationFrame 是浏览器用于定时循环操作的一个接口,类似于setTimeout,主要用途是按帧对网页进行重绘。设置这个API的目的是为了让各种网页动画(
Dom动画,Canvas动画,SVG动画,WebGL动画)能够有一个统一的刷新机制。显示器固有的刷新频率(60HZ/75HZ),requestAnimationFrame的基本思想就是与这个刷新频率保持一致。
 requestIdleCallback((deadline)=>{
       // 可用时间 & 是否有可用时间
     console.log(deadline.timeRemaining(),deadline.didTimeOut)  
  }) 
      //timieRemaining can change as soon as browser gets some work to do,so it should be constanly checked。
要知道浏览器渲染一帧后的剩余时间,除了浏览器本身,利用js基本很难准确计算得到,因为当requestAnimationFrame的回调完成后,还要进行样式的计算,布局,渲染以及浏览器内部的工作等,还要确保当前没有用户交互。
当事件很多时,你可能会担心你的回调函数永远不会被执行。requestIdleCallback有一个可选的第二参数:含有timeout属性的对象。如果设置了这个timeout的值,回调函数还没有调用的话,则浏览器必须在设置的这个毫秒数后,去强制调用对应的回调函数。如果回调函数由timeout触发,timeRemaining()会返回0,deadtime对象的didTimeout属性值true。(设置timeout会破坏潜规则)
如果在某一帧的末尾,回调函数被触发,它将被安排在当前帧被Commit之后,这表示相应的样式已经改动,同时更重要的是布局已经重新计算。如果我们在这个回调中进行样式的改动,设计到布局的计算则会被判无效。如果在下一帧中有任何读取布局相关的操作,浏览器不得不执行一次强制同步布局,这将是一个潜在的性能瓶颈。此外不要在回调函数中触发dom改动的原因是DOM改动的时间是不可预期的,正因为如此,DOM操作很容易地超过浏览器给出的限期。
最佳实践是在requestAnimationFrame的回调中去进行dom改动,因为浏览器会优化同类型的改动。这意味可以在下一次的requestAnimationFrame回调中添加一个文档片段。如果用的是虚拟DOM的库,你可在requestIdleCallback中做改动,但要在下一次requestAnimationFrame中将这些改动应用到Dom上,而不是在requestIdleCallback中。
为了使用这些API,需要把整个tree的渲染工作划分为可逐渐递增的单元。同时为了达到这个目标,React需要重新实现遍历树的算法,从之前的依赖内部调用栈的同步递归模型转向通过指针链接链表的异步模型。原因在于如果依赖内部调用栈,遍历更新的工作会一直进行直到调用栈为空。React Filter的目标在于把一次连续的树的遍历操作变为可单步执行的栈的片段,以达到无阻塞渲染和优先级渲染的目的。
(调用堆栈是一种数据结构,用来存储有关计算机程序活跃子程序的信息,调用堆栈存在的主要原因是跟踪每个活跃的子程序在完成执行时应该返回的控制位置。)
递归的算法非常直观,适合进行树的遍历操作,但是它是有局限性的,最大的一点就是无法分解工作为增量单元,这使得React不能暂停特定组件的工作并在稍后恢复。通过递归,React只能不断迭代直到它处理完成所有组件,堆栈为空。
React Fiber采用邻接链表树遍历算法来代替递归遍历。
// 结构体
class Node{
constructor(instance){
this.instance = instance;
this.child = null; // fisrt child
this.sibling = null; // first sibling
this.return = null; // parent
}
} function link(parent,elements){
if(elements === null) elements =[];
parent.child = elements.reduceRight((previous,current)=>{
const node = new Node(current);
node.return = parent;
node.sibling = previous;
return node;
},null)
return parent.child
}
// the function iterates over the array of nodes starting from the last one and links them //together in a singly linked list. It returns the reference
// to the first sibling in the list
function doWork(node){
const children = node.instance.render()
return link(node,children)
}
//it's parent first,depth-first implementation
function walk(o){
let root = o;
let current = o;
while(true){
let child = doWork(current);
if(child){
current = child
continue;
}
if(current === root){
return ;
}
while(!current.sibling){
if(!current.return || current.return === root){
return;
}
current = current.return;
}
}
}
// Fiber is re-implementation of the stack,specialized for React components.You can think of a //single fiber as a virtual stackframe
// we can stop the traversal at any time and resume to it later.That's exactly the condition we //wanted to achieve to be able to use the new
// requestIdleCallback API
function workLoop(isYieldy){
if(!isYieldy){
while(nextUnitOfWork !== null){
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
}else{
while(nextUnitOfWork !== null && !shouldYiels()){
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
}
}
// nextUnitOfWork 变量作为顶部帧,保留对当前Fiber节点的引用。函数shouldYield返回基于//deadlineDidExpire和deadline变量的结果,这些变量在React为Fiber节点执行
// 工作时不停的更新
react更新UI,要做的所有操作都是Fiber的要做工作。work的类型依据元素的类型。在协调期间,每个从render 函数返回的react节点,会被合并到fiber节点树中。每一个react节点都有对应的fiber节点,不同的是,fiber并不会在每次重新render之后,重新创建。fiber节点其实是可变的数据结构保存了组件的状态的dom结构。每个fiber可以认为代表了不同类型的工作要做。这使得fiber可以根据优先级进行渲染。
React 使用链表遍历组件树的更多相关文章
- React之使用Context跨组件树传递数据
		--------------------------------- 讲解一 原文:https://blog.csdn.net/xuxiaoping1989/article/details/78480 ... 
- React.js 小书 Lesson8 - 组件的组合、嵌套和组件树
		作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson8 转载请注明出处,保留原文链接和作者信息. 继续拓展前面的例子,现在我们已经有了 Heade ... 
- 前端笔记之React(二)组件内部State&React实战&表单元素的受控
		一.组件内部的State 1.1 state state叫状态,是每一个类式组件都有的属性,但函数式组件,没有state. state是一个对象,什么值都可以定义. 在任何类式组件的构造函数中,可以用 ... 
- React和Vue的组件更新比较
		p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 24.0px "Helvetica Neue"; color: #404040 } p. ... 
- 从 Vue 的视角学 React(四)—— 组件传参
		组件化开发的时候,参数传递是非常关键的环节 哪些参数放在组件内部管理,哪些参数由父组件传入,哪些状态需要反馈给父组件,都需要在设计组件的时候想清楚 但实现这些交互的基础,是明白组件之间参数传递的方式, ... 
- react基本语法及组件
		一.react简介 1.起源:React 起源于 Facebook 的内部项目,用来架设 Instagram 的网站,并于 2013 年 5 月开源. 2.特点: 1.声明式设计 −React采用声明 ... 
- 玩转 React(五)- 组件的内部状态和生命周期
		文章标题总算是可以正常一点了-- 通过之前的文章我们已经知道:在 React 体系中所谓的 "在 JavaScript 中编写 HTML 代码" 指的是 React 扩展了 Jav ... 
- react实战系列 —— react 的第一个组件
		react 的第一个组件 写了 react 有一个半月,现在又有半个月没写了,感觉对其仍旧比较陌生. 本文分两部分,首先聊一下 react 的相关概念,然后不使用任何语法糖(包括 jsx)或可能隐藏底 ... 
- react native之组织组件
		这些组件包括<TabView>,<NavigatorView>和<ListView>,他们实现了手机端最常用的交互和导航.你会发现这些组件在实际的项目中会非常有用. ... 
- POJ 3321 Apple Tree(后根遍历将树转化成序列,用树状数组维护)
		题意:一棵树,有很多分叉,每个分叉上最多有1个苹果. 给出n,接下来n-1行,每行u,v,表示分叉u,v之间有树枝相连.这里数据中u相当于树中的父节点,v相当于子节点. 给出两个操作: 1.C x ... 
随机推荐
- 花了半个小时基于 ChatGPT 搭建了一个微信机器人
			相信大家最近被 ChatGPT 刷屏了,其实在差不多一个月前就火过一次,不会那会好像只在程序员的圈子里面火起来了,并没有被大众认知到,不知道最近是因为什么又火起来了,而且这次搞的人尽皆知. 想着这么火 ... 
- vue中央事件
			详情请看这个链接https://blog.csdn.net/sinat_17775997/article/details/59025563 
- JZOJ 2114. 【GDKOI2011】反恐任务
			\(\text{Problem}\) 给定一张无向图,\(q\) 次询问,删去一个点或一条相邻两点间的边,问两点是否连通 询问独立 \(\text{Solution}\) 明显的用圆方树把图变成树 然 ... 
- 基于pytorch实现模型剪枝
			一,剪枝分类 1.1,非结构化剪枝 1.2,结构化剪枝 1.3,本地与全局修剪 二,PyTorch 的剪枝 2.1,pytorch 剪枝工作原理 2.2,局部剪枝 2.2.1,局部非结构化剪枝 2.2 ... 
- CPU持续100%分析并解决
			背景 接到客户电话,说某系统数据库服务器CPU利用率从上午8点开始到现在基本上都是100%的情况,影响到了业务.让给看看是什么原因导致的,怎么紧急处理一下. 现象 查看CPU利用率,确实一直是90%以 ... 
- 【磐河旅行】之酒店API接口对接实录
			1.项目需求概述: 通过对接第三方磐河旅行的酒店API接口实现在我们的APP .微信小程序.H5上可提供用户酒店查询.酒店预订.退订等功能.效果如下图: 2.酒店接口功能拆分 除了酒店静态数据字典(如 ... 
- java环境变量(Windows 11)
			1.下载JDK,之后安装 建议安装java8或者java11,稳定,大家都在用 下载网址:https://www.oracle.com/java/technologies/downloads/#jav ... 
- JavaScript类
			一.什么是js类 类是创建对象的模板,使用class关键字, 类体在大括号{}中,类体中我们可以写需要的属性.方法成员,其中每个类都包含一个特殊方法constructor().它是类的构造函数,由cl ... 
- fields设计与测试
			菲尔兹管理用例 一.向开发找到需求ID 需求设计文档ID 二.在fields的需求列表中,填入ID,创建计划 1.状态:测试接手 2.日期:冒烟日期,测试日期 3.可能遇到的问题: * 搜不到ID ... 
- MySQL8.0 存储引擎(InnoDB )buffer pool的实现原理
			数据库为了高效读取和存储物理数据,通常都会采用缓存的方式来弥补磁盘IO与CPU运算速度差.InnoDB 作为一个具有高可靠性和高性能的通用存储引擎也不例外,Buffer Pool就是其用来在内存中 ... 
