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 ...
随机推荐
- SQLSERVER 快照隔离级别 到底怎么理解?
一:背景 1. 讲故事 上一篇写完 SQLSERVER 的四个事务隔离级别到底怎么理解? 之后,有朋友留言问什么时候可以把 snapshot 隔离级别给补上,这篇就来安排,快照隔离级别看起来很魔法,不 ...
- 【MRTK】HoloLens开发基础项目设置
前言 好记性不如烂笔头,之前做项目的时候很熟练很顺手就没有写笔记.因为排期问题项目中断几个月之后需要重新拾起来,结果发现自己现在忘记得差不多了,于是还是决定写点东西记录一下.即便是简单的项目设置,忘记 ...
- 普冉PY32系列(四) PY32F002/003/030的时钟设置
目录 普冉PY32系列(一) PY32F0系列32位Cortex M0+ MCU简介 普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode开发环境 普冉PY32系列(三) P ...
- .Net Framework创建grpc
1.环境要求 .Net Framework 4.8 .Net Core 版本: https://www.cnblogs.com/dennisdong/p/17120990.html 2.Stub和Pr ...
- vue的异步组件
异步组件 异步组件:可以在首页加载之前先加载的组件,主要是做性能优化,提高用户体验 一.基本用法 在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件.Vue 提供了 de ...
- U-Boot-基础概念与学习分享
U-Boot 基础概念与学习分享 Board: rockchip-px30, armv8, Cortex-A35 U-Boot: rockchip-linux/u-boot, branch next- ...
- 使用 Three.js 的 3D 制作动画场景
推荐:将 NSDT场景编辑器 加入你的3D开发工具链. 由于 GSL 语法的复杂性,对于许多开发人员来说 WebGL 是一个未知的领域.但是有了 Three.js,在浏览器中 3D 的实现变得简单.下 ...
- Canvas:绘制多边形
前言 CanvasRenderingContext2D 没有提供绘制多边形的函数,所以只能由我们自己来实现绘制多边形的函数.以六边形为基础,需要用到三角函数:sin 和 cos. 点 A 坐标 (一) ...
- PostgreSQL事务隔离级别
一.概念 并发控制是多个事务在并发运行时,数据库保证事务一致性(Consistency)和隔离性(Isolation)的一种机制.PostgreSQL使用了多版本并发控制技术的一种变体:快照隔离San ...
- 一键接入 ChatGPT,让你的QQ群变得热闹起来
目录 项目效果 安装环境 配置文件 启动Mirai 启动ChatGPT 项目效果 ChatGPT 的出现对于人们的生活和工作都有着重要的影响,作为一个强大的自然语言处理模型,可以理解和生成自然语言,所 ...