本文为稀土技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
大家好,我是小杜杜,在React v16
以上的版别引入了一个十分重要的概念,那便是fiber
,实际上fiber
是react
团队花费两年的时刻重构的架构,在之前的文章中也提及到了fiber
,那么fiber
架构究竟是什么,为什么要使用fiber
在正式开端前,咱们能够先看看以下几个问题:
- 什么是
fiber
,fiber
处理了什么问题? -
fiber
中,保存了哪些信息,这些信息的作用是什么? - 怎么将
jsx
转化为fiber
链表,又是怎么将链表链接起来的 -
fiber
是怎么更新的? -
fiber
与stack
比较,首要处理了哪些方面的问题? - …
先附上今天的学习图谱,便利咱们更好的了解:
本文基于React v17.0.1源码
走进 Fiber
什么是Fiber
在一个巨大的项目中,如果有某个节点产生改动,就会给diff
带来巨大的压力,此刻想要要找到实在改动的部分就会消耗很多的时刻,也便是说此刻,js
会占据主线程去做比照,导致无法正常的页面烘托,此刻就会产生页面卡顿、页面呼应变差、动画、手势等应用作用差
为了处理这一问题,react
团队花费两年时刻,重写了react
的中心算法reconciliation
,在v16
中发布,为了区分reconciler
(谐和器),将之前的reconciler
称为stack reconciler
,之后称作fiber reconciler
(简称:fiber
)
简而言之,fiber
便是v16
之后的虚拟DOM
(React
在遍历的节点的时分,并不是实在的DOM
,而是选用虚拟的DOM
)
v16之前,React是怎么遍历节点的?
咱们先看看下面这张图:
遍历的顺序为:A => B => D => E => C => F => G
在v16
之前,react
选用的是深度优先遍历去遍历节点,转化为代码为:
const root = {
key: 'A',
children: [
{
key: 'B',
children: [
{
key: 'D',
},
{
key: 'E',
},
],
},
{
key: 'C',
children: [
{
key: 'F',
},
{
key: 'G',
},
],
},
],
};
const walk = dom => dom.children.forEach(child => walk(dom));
walk(root);
能够看出这种遍历采取的递归
遍历,如果这颗树十分的巨大,那么对应的栈也会越来越深,如果其中产生中止,那么整颗树都不能恢复。
也便是说,在传统的办法中,在寻觅节点的过程中,花费了1s,那么这1s便是浏览器无法呼应的,同时树越巨大,卡顿的作用也就越显着
所以在v16
之前的版别,无法处理中止和树巨大的问题
知悉fiber
在上面的介绍中,咱们知道fiber
实际上是一种中心算法,为了处理中止和树巨大的问题,那么接下来咱们先来了解下fiber
虚拟DOM是怎么转化成fiber的
先看看最常见的一段jsx
代码:
const Index = (props)=> {
return (
<div>
大家好,我是小杜杜
</div>
);
}
这段代码便是最一般的jsx
,经过babel
会编译成React.createElement
的方式
再来看看绑定的结构:
ReactDOM.render(
<App />,
document.getElementById('root')
);
之后会走一个beginWork
的办法,这个办法会经过tag
去判别这段代码的element
目标,再之后会调用reconcileChildFibers
函数,这个函数便是转化后的fiber
结构
element、fiber和DOM元素 的关系
-
element
目标便是咱们的jsx
代码,上面保存了props
、key
、children
等信息 -
DOM元素
便是最终呈现给用户展现的作用 - 而
fiber
便是充当element
和DOM元素
的桥梁,简略的说,只要elemnet
产生改动,就会经过fiber
做一次谐和,使对应的DOM
元素产生改动
其中有一个
tag
,这个tag
的类型便是判别element
对应那种的fiber
,如:
beginWork 的入参
在这儿我将这三个入参独自说一下,因为这三个参数比较重要,咱们要有一个基础的概念
- current:在视图层烘托的树
-
workInProgress:这个参数尤为重要,它便是在整个内存中所构建的
Fiber
树,一切的更新都产生在workInProgress
中,所以这个树是最新状况的,之后它将替换给current
- renderLanes:跟优先级有关,优先级也是一块十分大的点,这儿先不介绍
element 和 fiber 的对应表
在这儿总结了一些比较常用的对照表,供大家参考:
fiber | element |
---|---|
FunctionComponent = 0 |
函数组件 |
ClassComponent = 1 |
类组件 |
IndeterminateComponent = 2 | 初始化的时分不知道是函数组件还是类组件 |
HostRoot = 3 | 根元素,经过reactDom.render()产生的根元素 |
HostPortal = 4 | ReactDOM.createPortal 产生的 Portal |
HostComponent = 5 | dom 元素(如<div> ) |
HostText = 6 | 文本节点 |
Fragment = 7 |
<React.Fragment> ) |
Mode = 8 | <React.StrictMode> |
ContextConsumer = 9 | <Context.Consumer> |
ContextProvider = 10 | <Context.Provider> |
ForwardRef = 11 | React.ForwardRef |
Profiler = 12 | <Profiler> |
SuspenseComponent = 13 | <Suspense> |
MemoComponent = 14 |
React.memo 回来的组件 |
SimpleMemoComponent = 15 |
React.memo 没有制定比较的办法,所回来的组件 |
LazyComponent = 16 | <lazy /> |
fiber 保存了什么?
接下来咱们看看fiber
中保存了什么,如:
源码部分在packages/react-reconciler/src/ReactFiber.old.js
中的FiberNode
在这儿咱们直接来看看对应的type
(方位在同目录下的ReactInternalTypes.js
)
然后简略的分为四个部分,分别是Instance
、Fiber
、Effect
、Priority
Instance
Instance:这个部分是用来存储一些对应element
元素的特点
export type Fiber = {
tag: WorkTag, // 组件的类型,判别函数式组件、类组件等(上述的tag)
key: null | string, // key
elementType: any, // 元素的类型
type: any, // 与fiber相关的功用或类,如<div>,指向对应的类或函数
stateNode: any, // 实在的DOM节点
...
}
Fiber
Fiber:这部分内容存储的是关于fiber
链表相关的内容和相关的props
、state
export type Fiber = {
...
return: Fiber | null, // 指向父节点的fiber
child: Fiber | null, // 指向第一个子节点的fiber
sibling: Fiber | null, // 指向下一个兄弟节点的fiber
index: number, // 索引,是父节点fiber下的子节点fiber中的下表
ref:
| null
| (((handle: mixed) => void) & {_stringRef: ?string, ...})
| RefObject, // ref的指向,可能为null、函数或目标
pendingProps: any, // 本次烘托所需的props
memoizedProps: any, // 前次烘托所需的props
updateQueue: mixed, // 类组件的更新队列(setState),用于状况更新、DOM更新
memoizedState: any, // 类组件保存前次烘托后的state,函数组件保存的hooks信息
dependencies: Dependencies | null, // contexts、events(事情源) 等依靠
mode: TypeOfMode, // 类型为number,用于描述fiber的模式
...
}
Effect
Effect:副作用相关的内容
export type Fiber = {
...
flags: Flags, // 用于记录fiber的状况(删去、新增、替换等)
subtreeFlags: Flags, // 当时子节点的副作用状况
deletions: Array<Fiber> | null, // 删去的子节点的fiber
nextEffect: Fiber | null, // 指向下一个副作用的fiber
firstEffect: Fiber | null, // 指向第一个副作用的fiber
lastEffect: Fiber | null, // 指向最终一个副作用的fiber
...
}
Priority
Priority: 优先级相关的内容
export type Fiber = {
...
lanes: Lanes, // 优先级,用于调度
childLanes: Lanes,
alternate: Fiber | null,
actualDuration?: number,
actualStartTime?: number,
selfBaseDuration?: number,
treeBaseDuration?: number,
...
}
链表之间怎么衔接的?
在 Fiber
中咱们看到有return
、child
、sibling
这三个参数,分别指向父级、子级、兄弟,也便是说每个element
经过这三个特点进行衔接
举个栗子:
const Index:React.FC<any> = (props)=> {
return (
<div>
大家好,我是小杜杜
<div>走进fiber的国际</div>
<p>保藏 === 学会</p>
</div>
);
}
那么依照之前讲的就会转化为:
Fiber 执行阶段
初始化(mount)阶段
在上文现已说过,react
初次执行(初始化阶段)会以ReactDOM.render
为入口,然后开端执行,由于调用的函数实在过多,这儿我就简化一些,便利咱们更好了解
createFiber
createFiber:这个函数会创立rootFiber
,也便是react
应用的根,会调用FiberNode
函数来进行对应的构建作业
方位:packages/react-reconciler/src/ReactFiberRoot.old.js
const createFiber = function(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
): Fiber {
// $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
return new FiberNode(tag, pendingProps, key, mode);
};
而FiberNode便是上述讲的结构函数
createFiberRoot
createFiberRoot:它会调用FiberRootNode
结构函数,创立fiberRoot
,并且指向实在的根节点(root
)
方位:packages/react-reconciler/src/ReactFiberRoot.old.js
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
const uninitializedFiber = createHostRootFiber(tag);
root.current = uninitializedFiber; // 指向rootFiber
uninitializedFiber.stateNode = root; // 指向fiberRoot
initializeUpdateQueue(uninitializedFiber);
return root;
}
顺便看下FiberRootNode
函数
beginWork
beginWork:这个函数正真走咱们的jsx
代码,也便是上面讲解的链表之间怎么衔接的部分
那么咱们再把上面的图,拿出来,看看遍历的流程:
首要,咱们要知道
react
对fiber
结构的创立和更新都是深度优先遍历
- 首要会判别当时组件是类组件还是函数式组件,类组件
tag
为1,函数式为0 - 然后发现
div
标签,符号tag
为 5 - 发现
div
下包含三个部分,分别是,文本:大家好,我是小杜杜
、div标签
、p标签
- 首要遍历文本:
大家好,我是小杜杜
,下面无节点,符号tag
为 6 - 在遍历
div标签
,符号tag为 5
,此刻下面有节点,所以对节点进行遍历,也便是文本走进fiber的国际
,符号tag
为 6 - 同理最终遍历
p标签
整个的流程便是这样,经过tag
符号归于哪种类型,然后经过return
、child
、sibling
这三个参数来判别节点的方位
更新(Update)阶段
接下来看看更新阶段,举个例子:
const Index:React.FC<any> = (props)=> {
const [count, setCount] = useState(0)
return (
<div>
<div>数字:{count}</div>
<Button onClick={() => setCount(v => v + 1)} >点击</Button>
</div>
);
}
当咱们点击按钮后,会走createWorkInProgress
办法,这个办法会将创立一个新的workInProgress fiber
,然后还是会深度优先遍历,对产生改动的fiber
打上不同的flags
副作用标签,然后经过副作用(Effect)
中的nextEffect
、firstEffect
、lastEffect
等字短行程一个Effect List
的链表
再来看对应的源码(方位在packages/react-reconciler/src/ReactFiber.old.js
):
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate; //以alternate作为基础
if (workInProgress === null) { // 这儿判别是初始化还是更新阶段
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
...
// 这部分是重置一切的副作用
workInProgress.flags = current.flags & StaticMask;
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
// 对依靠的克隆
const currentDependencies = current.dependencies;
workInProgress.dependencies =
currentDependencies === null
? null
: {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
};
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
...
return workInProgress;
}
总的来说在更新阶段,更新阶段会将current
的alternate
作为基础,然后仿制一部分,进行节点的更新,回来一个新的workInProgress
这儿需要注意一点,
current fiber
和workInProgress fiber
中的alternate
是相互指向的,当新的workInProgress fiber
创立完成后,fiberRoot
的current
字段会从current fiber
中的rootFiber
变为workInProgress fiber
中的rootFiber
fiber 带来后的改动
最终,咱们再来看看更改前后的fiber图,产生了怎样的改动:
Stack Example
Fiber Example
能够看出来,fiber
显着比stack
要流畅很多,代表呼应速度变快,宽度的改动也不会引发卡顿
比照Vue
咱们经过上面的了解,现已知道React Fiber
实际上是无差别刷新,他是将整个改动的树作为更改,而Vue
是准确的将改动的节点进行替换,那是不是说Vue
要强于React
?
其实这个话题十分有争议,就我个人而言,Vue
的准确的替换也是具有代价的,至于两者孰强孰弱,作为一个小白也欠好去多做评论,咱们首要是学习思想,毕竟思想才是最重要的
React
本身也会提供一些优化的办法,如useMemo
、useCallback
等,咱们一定要善用于这些API,帮助咱们更好的去玩React
End
参考
- React 谐和(Reconciler)原理了解
- 走进React Fiber的国际
其他好文
- 「React 深化」畅聊React 事情体系(v17、v18版别)
- 「React深化」一文吃透React v16事情体系
- 「React深化」一文吃透虚拟DOM和diff算法
结语
React Fiber
能够说是React
的柱石,很多方面都离不开它,学习fiber
是不可短少的一部分
实际上,这篇文章笔者现已推翻过两三次,首要原因是fiber
实际上十分大,里面涉及的概念也十分琐碎,加上关于fiber
的文章也十分多,我不知道该怎么更好的去呈现出来
其次,这篇文章算是个入门级的fiber
,fiber
比较难的概念都没有涉及到,阅读起来相对轻松一点,比较难的是优先级、调度、谐和等模块
比较于更高档的模块,应该把架子搭起来,由浅入深,一点一点的慢慢啃,(如有不对的地方请在评论区留言指出~)
最终,感兴趣的能够关注下这个专栏,这个专栏会以进阶为目的,详细讲解React
相关的原理、源码、实战,有感兴趣的能够关注下,一同学习,一同进步~(别忘记点赞+保藏哦~)