React笔记-首次渲染
渲染机制
渲染机制主要分为两部分: 首次渲染和更新渲染。
首次渲染
首先通过一个小例子,来讲解首次渲染过程。
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>react app</title> </head> <body> <div id="root"></div> </body> </html>
import react from 'react'; import reactdom from 'react-dom'; class clickcounter extends react.component { constructor(props) { super(props); this.state = { count: 0 }; } handleclick = () => { this.setstate((state) => { return {count: state.count + 1}; }); } render() { return [ <button key="1" onclick={this.handleclick}>update counter</button>, <span key="2">{this.state.count}</span>, ] } } reactdom.hydrate(<clickcounter />, document.getelementbyid('root'));
程序运行到reactdom.hydrate
时,其中的<clickcounter />
已被babel
转换为react.createelement(clickcounter, null)
,生成的element
如下:
{ $$typeof: symbol(react.element), key: null, props: {}, ref: null, type: clickcounter }
接下来执行hydrate
函数,生成root
节点。首先了解下fiber
的部分数据结构。
- alternate(对应的
workinprogress
或fiber
) - statenode(关联的
fiber
,组件实例或者dom
节点) - type(组件或
html tag
,如div
,span
等) - tag(类型,详见worktags)
- effecttag(操作类型,详见sideeffecttag)
- updatequeue(更新队列)
- memoizedstate(
state
) - memoizedprops(
props
) - pendingprops(
vdom
) - return(父
fiber
) - sibling(兄弟
fiber
) - child(孩子
fiber
) - firsteffect(第一个待处理的
effect fiber
) - lasteffect(最后一个待处理的
effect fiber
)
首次渲染会以同步渲染的方式进行渲染,首先创建一个update
,将element
装载到其payload
属性中,然后合并到root.current.updatequeue
,如果没有updatequeue
会创建一个。我们暂且将root.current
看成hostroot
。
接着根据hostroot
克隆一棵workinprogress
更新树。将hostroot.alternate
指向workinprogress
,workinprogress.alternate
指向hostroot
。然后进入workloop
进行更新树操作部分。workloop
的任务也很简单,就是将所有节点的更新挂载到更新树上。下面详细看看reconciliation
阶段。
reconciliation阶段
reconciliation
的核心在于workloop
。workloop
会以workinprogress
为起点,即克隆的hostroot
,不断向下寻找。如果workinprogress.child
不为空,会进行diff
;如果为空会创建workinprogress.child`。
// 第一次循环nextunitofwork为workinprogress function workloop(isyieldy) { if (!isyieldy) { // flush work without yielding while (nextunitofwork !== null) { nextunitofwork = performunitofwork(nextunitofwork); } } else { // flush asynchronous work until there's a higher priority event while (nextunitofwork !== null && !shouldyieldtorenderer()) { nextunitofwork = performunitofwork(nextunitofwork); } } }
因为只涉及首次渲染,所以这里将performunitofwork
简单化。beginwork
根据workinprogress.tag
选择不同的处理方式。先暂且看看如何处理hostroot
。进入updatehostroot
方法,先进行workinprogress.updatequeue
的更新,计算新的state
,将update.basestate
和workinprogress.memoizedstate
指向新的state
。这里新的state
装载的是element
。
接下来调用createfiberfromelement
创建fiber
,将workinprogress.child
指向该fiber
,fiber.return
指向workinprogress
。
function performunitofwork(workinprogress) { let next = beginwork(workinprogress); // 创建workinprogress.child并返回 if (next === null) { // 没有孩子,收集effect list,返回兄弟或者父fiber next = completeunitofwork(workinprogress); } return next; } function beginwork(workinprogress) { switch(workinprogress.tag) { case hostroot: return updatehostroot(current, workinprogress, renderexpirationtime); case classcomponent: ... } }
用一张图体现更新树创建完成后的样子:
当workinprogress
没有孩子时,即创建的孩子为空。说明已经到达底部,开始收集effect
。
function completeunitofwork(workinprogress) { while (true) { let returnfiber = workinprogress.return; let siblingfiber = workinprogress.sibling; nextunitofwork = completework(workinprogress); ...// 省略收集effect list过程 if (siblingfiber !== null) { // if there is a sibling, return it // to perform work for this sibling return siblingfiber; } else if (returnfiber !== null) { // if there's no more work in this returnfiber, // continue the loop to complete the parent. workinprogress = returnfiber; continue; } else { // we've reached the root. return null; } } } function completework(workinprogress) { //根据workinprogress.tag创建、更新或删除dom switch(workinprogress.tag) { case hostcomponent: ... } return null; }
协调算法过程结束后,workinprogress
更新树更新完毕,收集的effect list
如下:
commit阶段
effect list
(链表)是reconciliation
阶段的结果,决定了哪些节点需要插入、更新和删除,以及哪些组件需要调用生命周期函数。firsteffect
记录第一个更新操作,firsteffect.nexteffect(fiber)
记录下一个,然后继续通过其nexteffect
不断往下寻找直至为null
。下面是commit阶段的主要流程:
// finishedwork为更新树 function commitroot(root, finishedwork) { commitbeforemutationlifecycles(); commitallhosteffects(); root.current = finishedwork; commitalllifecycles(); }
变量nexteffect
每次执行完上面一个函数会被重置为finishedwork
。
commitbeforemutationlifecycles
检查effect list
中每个fiber
是否有snapshot effect
,如果有则执行getsnapshotbeforeupdate
。
// 触发getsnapshotbeforeupdate function commitbeforemutationlifecycles() { while (nexteffect !== null) { const effecttag = nexteffect.effecttag; if (effecttag & snapshot) { const current = nexteffect.alternate; commitbeforemutationlifecycles(current, nexteffect); } nexteffect = nexteffect.nexteffect; } }
commitallhosteffects
提交所有effect
,实现dom
的替换、更新和删除。
function commitallhosteffects() { while(nexteffect !== null) { var effecttag = nexteffect.effecttag; var primaryeffecttag = effecttag & (placement | update | deletion); switch (primaryeffecttag) { case placement: { commitplacement(nexteffect); ... } case placementandupdate: { commitplacement(nexteffect); var _current = nexteffect.alternate; commitwork(_current, nexteffect); ... } case update: { var _current2 = nexteffect.alternate; commitwork(_current2, nexteffect); ... } case deletion: {// 触发componentwillunmout commitdeletion(nexteffect); ... } } nexteffect = nexteffect.nexteffect; } }
commitalllifecycles
触发componentdidmount
或componentdidupdate
function commitalllifecycles(finishedroot, committedexpirationtime) { while (nexteffect !== null) { var effecttag = nexteffect.effecttag; if (effecttag & (update | callback)) { var current$$1 = nexteffect.alternate; commitlifecycles(finishedroot, current$$1, nexteffect, committedexpirationtime); } if (effecttag & ref) { commitattachref(nexteffect); } if (effecttag & passive) { rootwithpendingpassiveeffects = finishedroot; } nexteffect = nexteffect.nexteffect; } }
总结
这里并未逐一细说,不想读起来直犯困,更多讲述了大概流程。如果觉得有疑惑的地方,也知道该在什么地方找到对应的源码,解答疑惑。
更好的阅读体验在我的,欢迎