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,所以就算反复中断,用记也不会看到更新一半的DOM
Fiber
在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
节点可以复用,只是需要交接下顺序。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。