转自 React 技术揭秘
React15
React15
架构能够分为 2 层:
Reconciler
(协调器)————负责找出变动的组件,diff
Renderer
(渲染器)————负责将变动的组件渲染到页面上
Reconciler
(协调器)
react
是通过 this.setState
,this.forceUpdate
,ReactDOM.render
等API
触发更新的。
每当有更新产生时,Reconciler
会做如下工作:
- 调用函数组件、或
class
组件的render
办法,将返回的JSX
转化为虚构DOM
- 将虚构
DOM
和上次更时的虚构DOM
比照 - 通过比照找出本次更新中变动的虚构
DOM
-
告诉
Renderer
将变动的虚构DOM
渲染到页面上Renderer
(渲染器)因为
react
反对跨平台,所以不同平台有不同的Renderer
,浏览器的是ReactDOM
因为用递归执行,所以没方法中断,当层级很深时,递归更新工夫超过了16ms
,用户交互就会卡顿
state=1;
<li>{state.count}</li>//<li>1</li>
<li>{state.count*2}</li>//<li>2</li>
当点一个 state+1
时更新步骤:
Reconciler
发现 1 须要变为 2,告诉Renderer
,Renderer
更新DOM
,1 变为 2。Reconciler
发现 2 须要变为 4,告诉Renderer
,Renderer
更新DOM
,2 变为 4。
能够看到,Reconciler
和 Renderer
是交替工作的,当第一个 li
在页面上曾经变动后。第二个 li
才进入Reconciler
。就是发现扭转渲染扭转,扭转就渲染的模式
React16
react16
的架构能够分为三层:
Scheduler
(调度器)————调度工作的优先级,高优工作优先进入Reconciler
Reconciler
(协调器)————负责找出变动的组件,diff
,又被称为render
阶段,在此阶段会调用组件的render
办法。-
Renderer
(渲染器)————负责将变动的组件渲染到页面上,又被称为commit
阶段,就像git commit
一样把render
阶段的信息提交渲染到页面上。render
与commit
阶段统称为work
。Scheduler
(调度器)以浏览器是否有剩余时间作为工作中断的规范,也须要当浏览器有剩余时间时来告诉到咱们,相似
API:requistIdCallback
。
就是判断浏览器有无剩余时间,如有按优先级继续执行Reconciler
Reconciler
(协调器)在
react15
是用递归来解决虚构DOM
,react16
的更新工作从递归变成能够中断的循环过程。/** @noinline */ function workLoopConcurrent() { // Perform work until Scheduler asks us to yield while (workInProgress !== null && !shouldYield()) {workInProgress = performUnitOfWork(workInProgress); } }
在
React16
中,Reconciler
和Renderer
不是交替工作,而是当Scheduler
将工作交给Reconciler
后,Reconciler
会将变动的虚构DOM
打上增 / 删 / 改的tag
。
整个Scheduler
与Reconciler
的工作都在内存中进行,只有当所有组件都实现Reconciler
的工作,才会对立交给Renderer
渲染Renderer(渲染器)
Renderer
依据Reconciler
为虚构DOM
打的标记,同步执行对应的DOM
操作。
state=1;
<li>{state.count}</li>//<li>1</li>
<li>{state.count*2}</li>//<li>2<li>
当点一个 state+1
时更新步骤:
Scheduler
接管到更新,看下有没有其它高优先更新要执行,没有的放将state.count
从 1 变成 2,交给Reconciler
。Reconciler
接管到更新,找出须要变动的虚构DOM
,发现在 1 要变成 2 打tag:Update
, 又发现了 2 要变成 4 再给第二个打上tag:Update
。都完了之后将打了标识的虚构DOM
给Renderer
。-
Renderer
接管到告诉,找到打了Update
标识的 2 个虚构DOM
,对它们执行更新DOM
的操作。
2,3 步可随时因为有其它高优先级工作先更新或没有剩余时间而中断,但因为 2,3 都是在内存中进行,不会更新页面上的 DOM,所以就算重复中断,用记也不会看到更新一半的 DOMFiber
在
react15
及之前,Reconciler
采纳递归的形式创立虚构DOM
,递归不能中断,如果组件树层级很深,递归工夫就多,线程开释不进去,就会造成卡顿,因为数据保留在递归栈中被称为stack Reconciler
。react16
将递归的无奈中断更新重构为异步的可中断更新,反对工作不同优先级,可中断与复原,复原后可利用之前的中间状态。每个工作更新单元为React Element
对应的Fiber
节点,基于Fiber
节点实现叫Fiber Reconciler
Fiber
的构造function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ) { // 作为静态数据构造的属性 this.tag = tag; this.key = key; this.elementType = null; this.type = null; this.stateNode = null; // 用于连贯其余 Fiber 节点造成 Fiber 树 this.return = null;// 指向父级 Fiber 节点 this.child = null;// 指向子 Fiber 节点 this.sibling = null;// 指向左边第一个兄弟 Fiber 节点 this.index = 0; this.ref = null; // 作为动静的工作单元的属性, 保留本次更新造成的状态扭转相干信息 this.pendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; this.memoizedState = null; this.dependencies = null; this.mode = mode; // 保留本次更新会造成的 DOM 操作 this.effectTag = NoEffect; this.nextEffect = null; this.firstEffect = null; this.lastEffect = null; // 调度优先级相干 this.lanes = NoLanes; this.childLanes = NoLanes; // 指向该 fiber 在另一次更新时对应的 fiber this.alternate = null; }
作为架构来说,每个
Fiber
节点有对应的React element
,多个Fiber
节点是靠this.return
,this.child
,this.sibling
3 个属性连接成树的
如组件构造对应的Fiber
树,用return
代指父节点function App() { return ( <div> i am <span>KaSong</span> </div> ) }
render
阶段顺次链式执行程序: rootFiber beginWork
App Fiber beginWork
div Fiber beginWork
"i am" Fiber beginWork
"i am" Fiber completeWork
span Fiber beginWork
span Fiber completeWork
div Fiber completeWork
App Fiber completeWork
-
rootFiber completeWoek
双缓存
Fiber
树在内存中构建并间接替换的技术叫双缓存
react
应用双缓存来实现Fiber
树的构建与替换 – 对应着DOM
树的创立与更新。
在react
中最多会同时存在 2 棵Fiber
树。以后屏幕上显示的Fiber
树称为current Fiber
,正在内存构建的称为workInProgress Fiber
。
当内存的workInprogress Fiber
树构建实现交给Renderer
渲染在页面上后,利用的要节点的current
指针指向workInProgress Fiber
树,workInProgress Fiber
树就变成了current Fiber
树。
每次状态更新都会产生新的workInProgress Fiber
树,通过current
与workInProgress
替换,实现DOM
更新。在构建workInProgress Fiber
树时会尝试复用current Fiber
树中已有的Fiber
节点内的属性,克隆current.child
作为workInProgress.child
,而不须要新建workInProgres.child
。
就是在组件 mount
时,Reconciler
依据 JSX
形容的组件内容生成组件对应的 Fiber
节点。在组件第一次 mount
的时候只有 rootFiber
上有插入的 tag
, 把Reconciler
生成的 DOM
树全副放在 rootFiber
下。
在 update
时,Reconciler
将 JSX
与Fiber
节点,保留的数据比照,生成组件对应的 Fiber
节点,并依据比照后果为 Fiber
节点打上标记。每个执行完 completeWork
且存在 effectTag
的Fiber
节点会被保留在 effectList
(只蕴含它的子孙节点)的单向链表中。在commit
阶段只有遍历 effectList
就能执行所有的 effect
了。
diff
为了升高复杂度,react
的 diff
预设了 3 个限度:
- 只对同级元素进行
diff
,如果一个DOM
节点在前后 2 次更新中逾越了层级,那么react
就不会复用它了。 - 2 个不同类型的元素会产生出不同的树,如果元素由
div
变为p
,react
会销毁div
及其子孙节点,并新建p
及其子孙节点。 -
开发者能够通过
key prop
来暗示哪些子元素在不同的渲染下保持稳定,如:// 更新前 <div> <p key="ka">ka</p> <h3 key="song">song</h3> </div> // 更新后 <div> <h3 key="song">song</h3> <p key="ka">ka</p> </div>
如果没有
key
,react
会认为div
的第一个子节点由p
变为h3
,第二个子节点由h3
变为p
。合乎第 2 条的规定,会销毁它并重建。
然而当咱们用key
指明了节点前后对应关系后,react
晓得key==='ka'
的p
在更新后还存在,所以DOM
节点能够复用,只是须要交接下程序。