1.[译] 如何自定义React渲染器-Hello World
2.React Fiber原理?
3.React设计原理,由浅入深解析 react18 源码(一)
[译] 如何自定义React渲染器-Hello World
本文详细阐述了如何创建一个基本的自定义React渲染器,并通过Hello World示例进行演示。首先,创建一个React项目,并在其中实现一个极简的成都源码熊DOM渲染器。接着,为了展示自定义渲染器的使用,将实现一个简单的计数器组件,并在其中替换默认的ReactDOM渲染器。
在App.js文件中添加计数器组件,展示基本功能。
在index.js文件中,使用自定义的渲染器MyCustomRenderer替换默认的ReactDOM渲染器。确保引入react-reconciler包,通过yarn add react-reconciler完成安装。
创建myCustomRenderer.js文件,实现渲染器的核心功能,主要为render函数,使用react-reconciler作为支持。
在index.js中,使用MyCustomRenderer.render方法替换ReactDOM.render,接收三个参数:ReactElement、domElement和回调函数,其中回调函数在本示例中被忽略。
引入react-reconciler包后,需配置hostConfig,打扑克网站源码免费下载包含回调函数,如getRootHostContext、supportsMutation等。其中,getRootHostContext函数作为hostConfig的一部分,用于维护实现renderer所需信息。
根据错误提示,补充hostConfig中空方法,如支持UI树操作、UI更新、prop更新等。
完成以上步骤后,即可实现一个基本的自定义React渲染器,满足Hello World级别的应用需求。该自定义渲染器简单实用,为后续复杂应用的渲染提供基础框架。完整源码可于GitHub仓库查看。
React Fiber原理?
Fiber的特点/作用
Fiber能够使得动画、布局和页面交互变得更加的流畅。
一:Fiber的概念
React Fiber是react执行渲染时的一种新的调度策略,JavaScript是单线程的,一旦组件开始更新,主线程就一直被React控制,这个时候如果再次执行交互操作,就会卡顿。
React Fiber就是游戏服务器官网源码通过对象记录组件上需要做或者已经完成的更新,一个组件可以对应多个Fiber。
在render函数中创建的React Element树在第一次渲染的时候会创建一颗结构一模一样的的Fiber节点树。不同的React Element类型对应不同的Fiber节点类型。一个React Element的工作就由它对应的Fiber节点来负责。
一个React Element可以对应不止一个Fiber,因为Fiber在update的时候,会从原来的Fiber(我们称为current)clone出一个新的Fiber(我们称之为alternate)。俩个Fiber diff出的变化(side effect)记录在alternate上。所以一个组件在更新时最多会有俩个Fiber与其对应,在更新结束后alternate会取代之前的current称为新的current节点。
React Fiber重构这种方式,渲染过程采用切片的方式,每执行一会儿,就歇一会儿。如果有优先级更高的任务到来以后呢,就会先去执行,降低页面发生卡顿的可能性,使得React对动画等实时性要求较高的场景体验更好。
二:什么是Fiber?
当js在处理大型计算的时候会导致页面出现卡帧的现象,更严重的会出现页面“假死”。所以在这些情况下,必然会导致动画丢帧、不连贯,用户体验就特别差。为了解决这个问题,我们可以将大型的一键提取网页css源码计算拆分成一个个小型计算,然后按照执行顺序异步调用,这样就不会长时间霸占线程,UI也能在俩次小型计算的执行间隙进行更新,从而给与用户及时的反馈,Fiber就是这样做的,并且以一种更高逼格的方式实现了。
Driving Idea
如果说v.0之前的React解决了HOW(如何用最少的DOM操作成本来update视图)的问题,那么这一次Fiber的出现,在这个基础上还解决了WHEN(何时update视图的哪一部分)的问题。
分片优先级!!!
基于上述这些原因,Fiber实现了一个虚拟调用栈,并给所有的update进行优先级排序,如下:
'use strict';
export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;
module.exports = {
NoWork: 0, // No work is pending.
SynchronousPriority: 1, // 用于控制文本输入。同步的副作用.
AnimationPriority: 2, //需要在下一帧之前完成.
HighPriority: 3, // 需要很快完成的互动才能产生反应.
LowPriority: 4, // 数据获取,或更新存储的结果.
OffscreenPriority: 5, // 将不可见,但做的工作,以防它成为可见.
};
然后根据这些update的优先级,来决定执行的顺序。
我们可以看到动画和页面交互都是优先级比较高的,这也是Fiber能够使得动画、布局和页面交互变得更加的流畅的原因之一。
可以把Priority分为同步和异步两个类别,同步优先级的宝塔线不叠加K线源码任务会在当前帧完成,包括SynchronousPriority和TaskPriority。异步优先级的任务则可能在接下来的几个帧中被完成,包括HighPriority、LowPriority以及OffscreenPriority。
React v.3.2的优先级,不再这么划分,分为三类:NoWork、sync、async,前两类可以认为是同步任务,需要在当前tick完成,过期时间为null,最后一类异步任务会计算一个。
expirationTime,在workLoop中,根据过期时间来判断是否进行下一个分片任务,scheduleWork中更新任务优先级,也就是更新这个expirationTime。至于这个时间怎么计算,可以查看源码。
三:Fiber的基本原则:
更新任务分成俩个阶段,Reconcilition Phase(调和阶段)和Commit Phase(交付阶段)。Reconciliation Phase的任务干的事情是,找出要做的更新工作(Diff Fiber Tree),就是一个计算阶段,计算结果可以被缓存,也就可以被打断;Commit Phase需要提交所有更新并渲染,为了防止页面抖动,被设置为不能打断。
PS:componentWillMount
omponentWillReceiveProps componentWillUpdate 几个生命周期方法,在Reconciliation Phase被调用,有被打断的可能(时间用尽等情况),所以可能被多次调用。其实shouldComponentUpdate 也可能被多次调用,只是它只返回true或者false,没有副作用,可以暂时忽略。
四:Fiber的数据结构
fiber是个链表,有child和sibing属性,指向第一个子节点和相邻的兄弟节点,从而构成fiber tree。return 属性指向其父节点。
更新队列,updateQueue,是一个链表,有first和last俩个属性,指向第一个和最后一个update对象。
每个fiber有一个属性updateQueue指向其对应的更新队列。
每个fiber(当前fiber可以称为current)有一个属性alternate,开始时指向一个自己的clone体,update的变化会先更新到alternate上,当更新完毕,alternate替换current。
五:Fiber的执行流程
用户操作引起setState被调用以后,先调用enqueueSetState方法,该方法可以划分成俩个阶段(个人理解),第一阶段Data Preparation,是初始化一些数据结构,比如fiber,updateQueue,update。
新的update会通过insertUpdateIntoQueue方法,根据优先级插入到队列的对应位置,ensureUpdateQueues方法初始化俩个更新队列,queue1和current.updateQueue对应,queue2和current.alternate.updateQueue对应。
第二阶段,Fiber Reconciler,就开始进行任务分片调度,scheduleWork首先更新每个fiber的优先级,这里并没有updatePriority这个方法,但是干了这件事。当fiber.return === null,找到父节点,把所有diff出的变化(side effect)归结到root上。
requestWork,首先把当前的更新添加到schedule list中(addRootToSchedule),然后根据当前是否为异步渲染(isAsync参数),异步渲染调用。scheduleCallbackWithExpriation方法,下一步高能!!
scheduleCallbackWithExpriation这个方法在不同环境,实现不一样,chrome等浏览器中使用requestIdleCallback API,没有这个API的浏览器中,通过requestAnimationFrame模拟一个requestIdCallback,来在浏览器空闲时,完成下一个分片的工作,注意,这个函数会传入一个expirationTime,超过这个时间活没干完,就放弃了。
执行到performWorkOnRoot,就是fiber文档中提到的Commit Phase和Reconciliation Phase俩阶段。
第一阶段Reconciliation Phase,在workLoop中,通过一个while循环,完成每个分片任务。
performUnitOfWork也可以分成俩阶段,蓝色框表示。beginWork是一个入口函数,根据workInProgress的类型去实例化不同的react element class。workInProgress是通过alternate挂载一些新属性获得的。
实例化不同的react element class时候会调用和will有关的生命周期方法。
completeUnitOfWork是进行一些收尾工作,diff完一个节点以后,更新props和调用生命周期方法等。
然后进入Commit Phase阶段,这个阶段不能被打断。
六:Fiber对开发者有什么影响?
componentWillMount,componentWillReceiveProps,componentWillUpdate几个生命周期方法不再安全,由于任务执行过程可以被打断,这几个生命周期可能会执行多次,如果它们包含副作用(比如Ajax),会有意想不到的bug。React团队提供了替换的生命周期方法。建议如果使用以上方法,尽量使用纯函数,避免以后踩坑。
需要关注react为任务片设置的优先级,特别是页面用动画的情况。
如果一直有更高的级别任务,那么fiber算法会先执行级别更高的任务,执行完毕后再通过callback回到之前渲染到一半的组件,从头开始渲染。(看起来放弃已经渲染完的生命周期,会有点不合理,反而会增加渲染时长,但是react确实是这么干的)
React设计原理,由浅入深解析 react 源码(一)
React设计原理详解:深入理解React 源码(一)
React的核心工具之一是jsx,它是一种语法扩展,开发者编写的代码会被Babel编译成ReactElement,进一步转化为FiberNode,这是一种虚拟DOM在React中的实现,它能表达组件状态和节点关系,同时具备可扩展性。 FiberNode的工作方式采用深度优先遍历(DFS)策略,递归地处理ReactElement。在渲染过程中,递归分为beginWork(开始工作)和completeWork(完成工作)两个阶段。在ReactDOM的createRoot和render方法中,scheduleUpdateOnFiber和processUpdateQueue负责更新和创建子fiber节点。 在commit阶段,关键步骤包括执行root上的mutation,以及对Host类型的FiberNode构建离屏DOM树。ChildReconciler的两个关键点是子ReactElement到子fiber的创建方式和flag标识的设置。最后,学习者需要注意的是,通过阅读本文,可以关注以下三点:理解jsx与FiberNode的关系
掌握React的递归渲染过程和commit阶段的子阶段
反思和分享你的学习体验,一起探讨React的深入知识
如果你觉得这篇文章有价值,别忘了在留言区分享你的见解,或者将其推荐给你的朋友。让我们一起深化对React 源码的理解。