We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
查看原文
React Fiber可以理解为:
React Fiber
React内部实现的一套状态更新机制。支持任务不同优先级,可中断与恢复,并且恢复后可以复用之前的中间状态。
React
优先级
中间状态
其中每个任务更新单元为React Element对应的Fiber节点。
React Element
Fiber节点
最早的Fiber官方解释来源于2016年React团队成员Acdlite的一篇介绍 (opens new window)。
Fiber
在React15及以前,Reconciler采用递归的方式创建虚拟DOM,递归过程是不能中断的。如果组件树的层级很深,递归会占用线程很多时间,造成卡顿。
React15
Reconciler
为了解决这个问题,React16将递归的无法中断的更新重构为异步的可中断更新,由于曾经用于递归的虚拟DOM数据结构已经无法满足需要。于是,全新的Fiber架构应运而生。
React16
Fiber包含三层含义:
stack Reconciler
Fiber Reconciler
React element
你可以从这里看到Fiber节点的属性定义 (opens new window)。虽然属性很多,但我们可以按三层含义将他们分类来看
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; this.child = null; this.sibling = null; this.index = 0; this.ref = null; // 作为动态的工作单元的属性 this.pendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; this.memoizedState = null; this.dependencies = null; this.mode = mode; 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节点是如何连接形成树呢?靠如下三个属性:
// 指向父级Fiber节点 this.return = null; // 指向子Fiber节点 this.child = null; // 指向右边第一个兄弟Fiber节点 this.sibling = null;
举个例子,如下的组件结构:
function App() { return ( <div> i am <span>KaSong</span> </div> ) }
对应的Fiber树结构:
Fiber树
这里需要提一下,为什么父级指针叫做return而不是parent或者father呢?因为作为一个工作单元,return指节点执行完completeWork(本章后面会介绍)后会返回的下一个节点。子Fiber节点及其兄弟节点完成工作后会返回其父级节点,所以用return指代父级节点。
return
parent
father
completeWork
作为一种静态的数据结构,保存了组件相关的信息:
// Fiber对应组件的类型 Function/Class/Host... this.tag = tag; // key属性 this.key = key; // 大部分情况同type,某些情况不同,比如FunctionComponent使用React.memo包裹 this.elementType = null; // 对于 FunctionComponent,指函数本身,对于ClassComponent,指class,对于HostComponent,指DOM节点tagName this.type = null; // Fiber对应的真实DOM节点 this.stateNode = null;
作为动态的工作单元,Fiber中如下参数保存了本次更新相关的信息,我们会在后续的更新流程中使用到具体属性时再详细介绍
// 保存本次更新造成的状态改变相关信息 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;
如下两个字段保存调度优先级相关的信息,会在讲解Scheduler时介绍。
Scheduler
// 调度优先级相关 this.lanes = NoLanes; this.childLanes = NoLanes;
注意 在2020年5月,调度优先级策略经历了比较大的重构。以expirationTime属性为代表的优先级模型被lane取代。详见这个PR(opens new window)
注意
在2020年5月,调度优先级策略经历了比较大的重构。以expirationTime属性为代表的优先级模型被lane取代。详见这个PR(opens new window)
expirationTime
lane
以上,我们了解了Fiber是什么,知道Fiber节点可以保存对应的DOM节点。
DOM节点
相应的,Fiber节点构成的Fiber树就对应DOM树。
DOM树
那么如何更新DOM呢?这需要用到被称为“双缓存”的技术。
DOM
当我们用canvas绘制动画,每一帧绘制前都会调用ctx.clearRect清除上一帧的画面。
canvas
ctx.clearRect
如果当前帧画面计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏。
为了解决这个问题,我们可以在内存中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,由于省去了两帧替换间的计算时间,不会出现从白屏到出现画面的闪烁情况。
这种在内存中构建并直接替换的技术叫做双缓存 (opens new window)。
React使用“双缓存”来完成Fiber树的构建与替换——对应着DOM树的创建与更新。
在React中最多会同时存在两棵Fiber树。当前屏幕上显示内容对应的Fiber树称为current Fiber树,正在内存中构建的Fiber树称为workInProgress Fiber树。
current Fiber树
workInProgress Fiber树
current Fiber树中的Fiber节点被称为current fiber,workInProgress Fiber树中的Fiber节点被称为workInProgress fiber,他们通过alternate属性连接。
current fiber
workInProgress fiber
alternate
currentFiber.alternate === workInProgressFiber; workInProgressFiber.alternate === currentFiber;
React应用的根节点通过使current指针在不同Fiber树的rootFiber间切换来完成current Fiber树指向的切换。
current
rootFiber
current Fiber
即当workInProgress Fiber树构建完成交给Renderer渲染在页面上后,应用根节点的current指针指向workInProgress Fiber树,此时workInProgress Fiber树就变为current Fiber树。
Renderer
每次状态更新都会产生新的workInProgress Fiber树,通过current与workInProgress的替换,完成DOM更新。
workInProgress
接下来我们以具体例子讲解mount时、update时的构建/替换流程。
mount时
update时
考虑如下例子:
function App() { const [num, add] = useState(0); return ( <p onClick={() => add(num + 1)}>{num}</p> ) } ReactDOM.render(<App/>, document.getElementById('root'));
ReactDOM.render
fiberRootNode
fiberRoot
<App/>
之所以要区分fiberRootNode与rootFiber,是因为在应用中我们可以多次调用ReactDOM.render渲染不同的组件树,他们会拥有不同的rootFiber。但是整个应用的根节点只有一个,那就是fiberRootNode。
fiberRootNode的current会指向当前页面上已渲染内容对应Fiber树,即current Fiber树。
fiberRootNode.current = rootFiber;
由于是首屏渲染,页面中还没有挂载任何DOM,所以fiberRootNode.current指向的rootFiber没有任何子Fiber节点(即current Fiber树为空)。
fiberRootNode.current
子Fiber节点
render阶段
JSX
在构建workInProgress Fiber树时会尝试复用current Fiber树中已有的Fiber节点内的属性,在首屏渲染时只有rootFiber存在对应的current fiber(即rootFiber.alternate)。
首屏渲染
rootFiber.alternate
commit阶段
此时DOM更新为右侧树对应的样子。fiberRootNode的current指针指向workInProgress Fiber树使其变为current Fiber 树。
current Fiber 树
p节点
workInProgress Fiber 树
和mount时一样,workInProgress fiber的创建可以复用current Fiber树对应的节点数据。
mount
这个决定是否复用的过程就是Diff算法,后面章节会详细讲解
The text was updated successfully, but these errors were encountered:
No branches or pull requests
查看原文
React Fiber
可以理解为:React
内部实现的一套状态更新机制。支持任务不同优先级
,可中断与恢复,并且恢复后可以复用之前的中间状态
。其中每个任务更新单元为
React Element
对应的Fiber节点
。Fiber的起源
在
React15
及以前,Reconciler
采用递归的方式创建虚拟DOM,递归过程是不能中断的。如果组件树的层级很深,递归会占用线程很多时间,造成卡顿。为了解决这个问题,
React16
将递归的无法中断的更新重构为异步的可中断更新,由于曾经用于递归的虚拟DOM数据结构已经无法满足需要。于是,全新的Fiber
架构应运而生。#Fiber的含义
Fiber
包含三层含义:React15
的Reconciler
采用递归的方式执行,数据保存在递归调用栈中,所以被称为stack Reconciler
。React16
的Reconciler
基于Fiber节点
实现,被称为Fiber Reconciler
。Fiber节点
对应一个React element
,保存了该组件的类型(函数组件/类组件/原生组件...)、对应的DOM节点等信息。Fiber节点
保存了本次更新中该组件改变的状态、要执行的工作(需要被删除/被插入页面中/被更新...)。#Fiber的结构
你可以从这里看到Fiber节点的属性定义 (opens new window)。虽然属性很多,但我们可以按三层含义将他们分类来看
#作为架构来说
每个Fiber节点有个对应的
React element
,多个Fiber节点
是如何连接形成树呢?靠如下三个属性:举个例子,如下的组件结构:
对应的
Fiber树
结构:#作为静态的数据结构
作为一种静态的数据结构,保存了组件相关的信息:
#作为动态的工作单元
作为动态的工作单元,
Fiber
中如下参数保存了本次更新相关的信息,我们会在后续的更新流程中使用到具体属性时再详细介绍如下两个字段保存调度优先级相关的信息,会在讲解
Scheduler
时介绍。Fiber的工作原理
以上,我们了解了
Fiber
是什么,知道Fiber节点
可以保存对应的DOM节点
。相应的,
Fiber节点
构成的Fiber树
就对应DOM树
。那么如何更新
DOM
呢?这需要用到被称为“双缓存”的技术。#什么是“双缓存”
当我们用
canvas
绘制动画,每一帧绘制前都会调用ctx.clearRect
清除上一帧的画面。如果当前帧画面计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏。
为了解决这个问题,我们可以在内存中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,由于省去了两帧替换间的计算时间,不会出现从白屏到出现画面的闪烁情况。
这种在内存中构建并直接替换的技术叫做双缓存 (opens new window)。
React
使用“双缓存”来完成Fiber树
的构建与替换——对应着DOM树
的创建与更新。#双缓存Fiber树
在
React
中最多会同时存在两棵Fiber树
。当前屏幕上显示内容对应的Fiber树
称为current Fiber树
,正在内存中构建的Fiber树
称为workInProgress Fiber树
。current Fiber树
中的Fiber节点
被称为current fiber
,workInProgress Fiber树
中的Fiber节点
被称为workInProgress fiber
,他们通过alternate
属性连接。React
应用的根节点通过使current
指针在不同Fiber树
的rootFiber
间切换来完成current Fiber
树指向的切换。即当
workInProgress Fiber树
构建完成交给Renderer
渲染在页面上后,应用根节点的current
指针指向workInProgress Fiber树
,此时workInProgress Fiber树
就变为current Fiber树
。每次状态更新都会产生新的
workInProgress Fiber树
,通过current
与workInProgress
的替换,完成DOM
更新。接下来我们以具体例子讲解
mount时
、update时
的构建/替换流程。#mount时
考虑如下例子:
ReactDOM.render
会创建fiberRootNode
(源码中叫fiberRoot
)和rootFiber
。其中fiberRootNode
是整个应用的根节点,rootFiber
是<App/>
所在组件树的根节点。之所以要区分
fiberRootNode
与rootFiber
,是因为在应用中我们可以多次调用ReactDOM.render
渲染不同的组件树,他们会拥有不同的rootFiber
。但是整个应用的根节点只有一个,那就是fiberRootNode
。fiberRootNode
的current
会指向当前页面上已渲染内容对应Fiber树
,即current Fiber树
。由于是首屏渲染,页面中还没有挂载任何
DOM
,所以fiberRootNode.current
指向的rootFiber
没有任何子Fiber节点
(即current Fiber树
为空)。render阶段
,根据组件返回的JSX
在内存中依次创建Fiber节点
并连接在一起构建Fiber树
,被称为workInProgress Fiber树
。(下图中右侧为内存中构建的树,左侧为页面显示的树)在构建
workInProgress Fiber树
时会尝试复用current Fiber树
中已有的Fiber节点
内的属性,在首屏渲染
时只有rootFiber
存在对应的current fiber
(即rootFiber.alternate
)。workInProgress Fiber树
在commit阶段
渲染到页面。此时
DOM
更新为右侧树对应的样子。fiberRootNode
的current
指针指向workInProgress Fiber树
使其变为current Fiber 树
。#update时
p节点
触发状态改变,这会开启一次新的render阶段
并构建一棵新的workInProgress Fiber 树
。和
mount
时一样,workInProgress fiber
的创建可以复用current Fiber树
对应的节点数据。workInProgress Fiber 树
在render阶段
完成构建后进入commit阶段
渲染到页面上。渲染完毕后,workInProgress Fiber 树
变为current Fiber 树
。The text was updated successfully, but these errors were encountered: