本篇是详细解读React DOM操作的第一篇文章,文章所讲的内容发生在commit阶段。

 React和DOM的那些事-节点删去算法(react删除dom) React DOM 前端 第1张

本篇是详细解读React DOM操作的榜首篇文章,文章所讲的内容产生在commit阶段。

Fiber架构使得React需求保护两类树结构,一类是Fiber树,另一类是DOM树。当删去DOM节点时,Fiber树也要同步改变。但请注意删去操作履行的机遇:在完结DOM节点的其他改变(增、改)前,要先删去fiber节点,防止其他操作被搅扰。 这是由于进行其他DOM操作时需求循环fiber树,此刻假如有需求删去的fiber节点却还没删去的话,就会产生紊乱。

  1. functioncommitMutationEffects(
  2. firstChild:Fiber,
  3. root:FiberRoot,
  4. renderPriorityLevel,
  5. ){
  6. letfiber=firstChild;
  7. while(fiber!==null){
  8. //首要进行删去
  9. constdeletions=fiber.deletions;
  10. if(deletions!==null){
  11. commitMutationEffectsDeletions(deletions,root,renderPriorityLevel);
  12. }
  13. //假如删去之后的fiber还有子节点,
  14. //递归调用commitMutationEffects来处理
  15. if(fiber.child!==null){
  16. constprimarySubtreeTag=fiber.subtreeTag&MutationSubtreeTag;
  17. if(primarySubtreeTag!==NoSubtreeTag){
  18. commitMutationEffects(fiber.child,root,renderPriorityLevel);
  19. }
  20. }
  21. if(__DEV__){/*...*/}else{
  22. //履行其他DOM操作
  23. try{
  24. commitMutationEffectsImpl(fiber,root,renderPriorityLevel);
  25. }catch(error){
  26. captureCommitPhaseError(fiber,error);
  27. }
  28. }
  29. fiberfiber=fiber.sibling;
  30. }
  31. }

fiber.deletions是render阶段的diff进程检测到fiber的子节点假如有需求被删去的,就会被加到这儿来。

commitDeletion函数是删去节点的进口,它经过调用unmountHostComponents完结删去。搞懂删去操作之前,先看看场景。

有如下的Fiber树,Node(Node是一个代号,并不指的某个详细节点)节点行将被删去。

  1. Fiber树
  2. div#root
  3. |
  4. <App/>
  5. |
  6. div
  7. |
  8. <Parent/>
  9. |
  10. Node
  11. |↖
  12. |↖
  13. P——————><Child>
  14. |
  15. a

经过这种场景能够推测出当删去该节点时,它下面子树中的一切节点都要被删去。现在直接以这个场景为例,走一下删去进程。这个进程实际上也便是unmountHostComponents函数的运行机制。

删去进程

删去Node节点需求父DOM节点的参加:

  1. parentInstance.removeChild(child)

所以首要要定位到父级节点。进程是在Fiber树中,以Node的父节点为起点往上找,找到的榜首个原生DOM节点即为父节点。在比如中,父节点便是div。尔后以Node为起点,遍历子树,子树也是fiber树,因而遍历是深度优先遍历,将每个子节点都删去。

需求特别注意的一点是,对循环节点进行删去,每个节点都会被删去操作去处理,这儿的每个节点是fiber节点而不是DOM节点。DOM节点的删去机遇是从Node开端遍历进行删去的时分,遇到了榜首个原生DOM节点(HostComponent或HostText)这个时间,在删去了它子树的一切fiber节点后,才会被删去。

以上是完好进程的简述,关于详细进程要清晰几个要害函数的责任和调用联系才行。删去fiber节点的是unmountHostComponents函数,被删去的节点称为方针节点,它的责任为:

  1. 找到方针节点的DOM层面的父节点
  2. 判别方针节点假如是原生DOM类型的节点,那么履行3、4,不然先卸载自己之后再往下找到原生DOM类型的节点之后再履行3、4
  3. 遍历子树履行fiber节点的卸载
  4. 删去方针节点的DOM节点

其间第3步的操作,是经过commitNestedUnmounts完结的,它的责任很单一也很清晰,便是遍历子树卸载节点。

然后详细到每个节点的卸载进程,由commitUnmount完结。它的责任是

  1. Ref的卸载
  2. 类组件生命周期的调用
  3. HostPortal类型的fiber节点递归调用unmountHostComponents重复删去进程

下面来看一下不同类型的组件它们的详细删去进程是怎样的。

区别被删去组件的类别

Node节点的类型有多种可能性,咱们以最典型的三种类型(HostComponent、ClassComponent、HostPortal)为例别离阐明一下删去进程。

首要履行unmountHostComponents,会向上找到DOM层面的父节点,然后依据下面的三种组件类型别离处理,咱们挨个来看。

HostComponent

Node 是HostComponent,调用commitNestedUnmounts,以Node为起点,遍历子树,开端对一切子Fiber进行卸载操作,遍历的进程是深度优先遍历。

  1. Delation-->Node(span)
  2. |↖
  3. |↖
  4. P——————><Child>
  5. |
  6. a

对节点逐一履行commitUnmount进行卸载,这个遍历进程其实关于三种类型的节点,都是相似的,为了节约篇幅,这儿只表述一次。

Node的fiber被卸载,然后向下,p的fiber被卸载,p没有child,找到它的sibling<Child>,<Child>的fiber被卸载,向下找到a,a的fiber被卸载。此刻到了整个子树的叶子节点,开端向上return。由a 到 <Child>,再回到Node,遍历卸载的进程完毕。

在子树的一切fiber节点都被卸载之后,才能够安全地将Node的DOM节点从父节点中移除。

ClassComponent

  1. Delation-->Node(ClassComponent)
  2. |
  3. |
  4. span
  5. |↖
  6. |↖
  7. P——————><Child>
  8. |
  9. a

Node是ClassComponent,它没有对应的DOM节点,要先调用commitUnmount卸载它自己,之后会先往下找,找到榜首个原生DOM类型的节点span,以它为起点遍历子树,确保每一个fiber节点都被卸载,之后再将span从父节点中删去。

HostPortal

  1. div2(ContainerOfNode)
  2. divcontainerInfo
  3. |↗
  4. |↗
  5. Node(HostPortal)
  6. |
  7. |
  8. span
  9. |↖
  10. |↖
  11. P——————><Child>
  12. |
  13. a

Node是HostPortal,它没有对应的DOM节点,因而删去进程和ClassComponent根本共同,不同的是删去它下面榜首个子fiber的DOM节点时不是从这个被删去的HostPortal类型节点的DOM层面的父节点中删去,而是从HostPortal的containerInfo中移除,图示上为div2,由于HostPortal会将子节点烘托到父组件以外的DOM节点。

以上是三种类型节点的删去进程,这儿值得注意的是,unmountHostComponents函数履行到遍历子树卸载每个节点的时分,一旦遇到HostPortal类型的子节点,会再次调用unmountHostComponents,以它为方针节点再进行它以及它子树的卸载删去操作,相当于一个递归进程。

commitUnmount

HostComponent 和 ClassComponent的删去都调用了commitUnmount,除此之外还有FunctionComponent也会调用它。它的效果对三种组件是不同的:

  • FunctionComponent 函数组件中一旦调用了useEffect,那么它卸载的时分要去调用useEffect的毁掉函数。(useLayoutEffect的毁掉函数是调用commitHookEffectListUnmount履行的)
  • ClassComponent 类组件要调用componentWillUnmount
  • HostComponent 要卸载ref
  1. functioncommitUnmount(
  2. finishedRoot:FiberRoot,
  3. current:Fiber,
  4. renderPriorityLevel:ReactPriorityLevel,
  5. ):void{
  6. onCommitUnmount(current);
  7. switch(current.tag){
  8. caseFunctionComponent:
  9. caseForwardRef:
  10. caseMemoComponent:
  11. caseSimpleMemoComponent:
  12. caseBlock:{
  13. constupdateQueue:FunctionComponentUpdateQueue|null=(current.updateQueue:any);
  14. if(updateQueue!==null){
  15. constlastEffect=updateQueue.lastEffect;
  16. if(lastEffect!==null){
  17. constfirstEffect=lastEffect.next;
  18. leteffect=firstEffect;
  19. do{
  20. const{destroy,tag}=effect;
  21. if(destroy!==undefined){
  22. if((tag&HookPassive)!==NoHookEffect){
  23. //向useEffect的毁掉函数行列里pusheffect
  24. enqueuePendingPassiveHookEffectUnmount(current,effect);
  25. }else{
  26. //测验运用try...catch调用destroy
  27. safelyCallDestroy(current,destroy);
  28. ...
  29. }
  30. }
  31. effecteffect=effect.next;
  32. }while(effect!==firstEffect);
  33. }
  34. }
  35. return;
  36. }
  37. caseClassComponent:{
  38. safelyDetachRef(current);
  39. constinstance=current.stateNode;
  40. //调用componentWillUnmount
  41. if(typeofinstance.componentWillUnmount==='function'){
  42. safelyCallComponentWillUnmount(current,instance);
  43. }
  44. return;
  45. }
  46. caseHostComponent:{
  47. //卸载ref
  48. safelyDetachRef(current);
  49. return;
  50. }
  51. ...
  52. }
  53. }

总结

咱们来复盘一下删去进程中的要点:

  • 删去操作履行的机遇
  • 删去的方针是谁
  • 从哪里删去

mutation在根据Fiber节点对DOM做其他操作之前,需求先删去节点,确保留给后续操作的fiber节点都是有用的。删去的方针是Fiber节点及其子树和Fiber节点对应的DOM节点,整个轨道循着fiber树,对方针节点和一切子节点都进行卸载,对方针节点对应的(或之下的榜首个)DOM节点进行删去。关于原生DOM类型的节点,直接从其父DOM节点删去,关于HostPortal节点,它会把子节点烘托到外部的DOM节点,所以会从这个DOM节点中删去。清晰以上三个点再结合上述整理的进程,就能够逐步理清删去操作的头绪。

转载请说明出处
知优网 » React和DOM的那些事-节点删去算法(react删除dom)

发表评论

您需要后才能发表评论